diff options
author | 2023-08-31 22:24:34 -0700 | |
---|---|---|
committer | 2023-08-31 22:24:34 -0700 | |
commit | 7dd06e2a12c1696bfbce715b6baaced3fa802944 (patch) | |
tree | e3d918c60f3358439e239cbcfb1432bf9001d988 | |
parent | 7528ea00843f7c33b8e3017ca28b83cca556f783 (diff) | |
download | bun-7dd06e2a12c1696bfbce715b6baaced3fa802944.tar.gz bun-7dd06e2a12c1696bfbce715b6baaced3fa802944.tar.zst bun-7dd06e2a12c1696bfbce715b6baaced3fa802944.zip |
insert `enumerable: true` when needed
-rw-r--r-- | src/bun.js/bindings/CommonJSModuleRecord.cpp | 14 | ||||
-rw-r--r-- | src/js_parser.zig | 117 |
2 files changed, 120 insertions, 11 deletions
diff --git a/src/bun.js/bindings/CommonJSModuleRecord.cpp b/src/bun.js/bindings/CommonJSModuleRecord.cpp index 007410dde..a1f5781d7 100644 --- a/src/bun.js/bindings/CommonJSModuleRecord.cpp +++ b/src/bun.js/bindings/CommonJSModuleRecord.cpp @@ -556,7 +556,7 @@ void JSCommonJSModule::toSyntheticSource(JSC::JSGlobalObject* globalObject, if (canPerformFastEnumeration(structure)) { exports->structure()->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool { auto key = entry.key(); - if (key->isSymbol() || entry.attributes() & PropertyAttribute::Accessor || entry.attributes() & PropertyAttribute::CustomAccessor || key == esModuleMarker) + if (key->isSymbol() || entry.attributes() & PropertyAttribute::DontEnum || key == esModuleMarker) return true; needsToAssignDefault = needsToAssignDefault && key != vm.propertyNames->defaultKeyword; @@ -568,7 +568,7 @@ void JSCommonJSModule::toSyntheticSource(JSC::JSGlobalObject* globalObject, }); } else { JSC::PropertyNameArray properties(vm, JSC::PropertyNameMode::Strings, JSC::PrivateSymbolMode::Exclude); - exports->methodTable()->getOwnPropertyNames(exports, globalObject, properties, DontEnumPropertiesMode::Include); + exports->methodTable()->getOwnPropertyNames(exports, globalObject, properties, DontEnumPropertiesMode::Exclude); if (catchScope.exception()) { catchScope.clearExceptionExceptTermination(); return; @@ -586,9 +586,6 @@ void JSCommonJSModule::toSyntheticSource(JSC::JSGlobalObject* globalObject, if (!exports->getPropertySlot(globalObject, property, slot)) continue; - if (slot.isAccessor() || slot.isUnset()) - continue; - exportNames.append(property); JSValue getterResult = slot.getValue(globalObject, property); @@ -609,7 +606,7 @@ void JSCommonJSModule::toSyntheticSource(JSC::JSGlobalObject* globalObject, } else if (canPerformFastEnumeration(structure)) { exports->structure()->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool { auto key = entry.key(); - if (key->isSymbol() || key == vm.propertyNames->defaultKeyword || entry.attributes() & PropertyAttribute::Accessor || entry.attributes() & PropertyAttribute::CustomAccessor) + if (key->isSymbol() || entry.attributes() & PropertyAttribute::DontEnum || key == vm.propertyNames->defaultKeyword) return true; JSValue value = exports->getDirect(entry.offset()); @@ -620,7 +617,7 @@ void JSCommonJSModule::toSyntheticSource(JSC::JSGlobalObject* globalObject, }); } else { JSC::PropertyNameArray properties(vm, JSC::PropertyNameMode::Strings, JSC::PrivateSymbolMode::Exclude); - exports->methodTable()->getOwnPropertyNames(exports, globalObject, properties, DontEnumPropertiesMode::Include); + exports->methodTable()->getOwnPropertyNames(exports, globalObject, properties, DontEnumPropertiesMode::Exclude); if (catchScope.exception()) { catchScope.clearExceptionExceptTermination(); return; @@ -638,9 +635,6 @@ void JSCommonJSModule::toSyntheticSource(JSC::JSGlobalObject* globalObject, if (!exports->getPropertySlot(globalObject, property, slot)) continue; - if (slot.isAccessor() || slot.isUnset()) - continue; - exportNames.append(property); JSValue getterResult = slot.getValue(globalObject, property); diff --git a/src/js_parser.zig b/src/js_parser.zig index 6351aa33b..d2764092f 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -12045,6 +12045,111 @@ fn NewParser_( return ExprListLoc{ .list = ExprNodeList.fromList(args), .loc = close_paren_loc }; } + /// In Node, this export will work: + /// + /// ``` + /// Object.defineProperty(exports, "foo", { + /// value: 10, + /// }); + /// ``` + /// + /// And this export will be a syntax error: + /// + /// ``` + /// Object.defineProperty(exports, "foo", { + /// enumerable: false, + /// value: 10, + /// }); + /// ``` + /// + /// The enumerable property normally defaults to false, but Node's `cjs-module-lexer` defaults to true, + /// so we need to add the enumerable property if it isn't explicitly set on the object. + /// + pub fn parseCallArgsForObjectDefineProperty(p: *P) anyerror!ExprListLoc { + const old_allow_in = p.allow_in; + p.allow_in = true; + defer p.allow_in = old_allow_in; + + var args = ListManaged(Expr).init(p.allocator); + try p.lexer.expect(.t_open_paren); + + var first = true; + var need_to_check_for_enumerable = false; + while (p.lexer.token != .t_close_paren) { + const loc = p.lexer.loc(); + const is_spread = p.lexer.token == .t_dot_dot_dot; + if (is_spread) { + // p.mark_syntax_feature(compat.rest_argument, p.lexer.range()); + try p.lexer.next(); + } + + var arg = try p.parseExpr(.comma); + + if (first) { + first = false; + switch (arg.data) { + .e_identifier => |ident| { + if (strings.eqlComptime(p.loadNameFromRef(ident.ref), "exports")) { + need_to_check_for_enumerable = true; + } + }, + .e_dot => |dot| { + if (dot.target.data == .e_identifier) { + const ident = dot.target.data.e_identifier; + if (strings.eqlComptime(dot.name, "exports") and strings.eqlComptime(p.loadNameFromRef(ident.ref), "module")) { + need_to_check_for_enumerable = true; + } + } + }, + else => {}, + } + } + + if (is_spread) { + arg = p.newExpr(E.Spread{ .value = arg }, loc); + } + args.append(arg) catch unreachable; + if (p.lexer.token != .t_comma) { + break; + } + try p.lexer.next(); + } + + if (args.items.len == 3 and need_to_check_for_enumerable and args.items[2].data == .e_object) { + var obj: *E.Object = args.items[2].data.e_object; + var prop_list = obj.properties.listManaged(p.allocator); + + var has_enumerable = false; + + for (prop_list.items) |prop| { + if (prop.key) |key| { + switch (key.data) { + .e_string => |prop_name| { + if (prop_name.isUTF8() and prop_name.end == null and prop_name.next == null and strings.eqlComptime(prop_name.data, "enumerable")) { + has_enumerable = true; + break; + } + }, + else => {}, + } + } + } + + if (!has_enumerable) { + try prop_list.append(Property{ + .value = p.newExpr(E.Boolean{ .value = true }, logger.Loc.Empty), + .key = p.newExpr(E.String{ .data = "enumerable" }, logger.Loc.Empty), + }); + + obj.properties = Property.List.fromList(prop_list); + } + } + + const close_paren_loc = p.lexer.loc(); + try p.lexer.expect(.t_close_paren); + return ExprListLoc{ .list = ExprNodeList.fromList(args), .loc = close_paren_loc }; + } + pub fn parseSuffix(p: *P, _left: Expr, level: Level, errors: ?*DeferredErrors, flags: Expr.EFlags) anyerror!Expr { var left = _left; var optional_chain: ?js_ast.OptionalChain = null; @@ -12299,7 +12404,17 @@ fn NewParser_( return left; } - const list_loc = try p.parseCallArgs(); + const list_loc: ExprListLoc = brk: { + if (left.data == .e_dot and left.data.e_dot.target.data == .e_identifier) { + const left_left_name = p.loadNameFromRef(left.data.e_dot.target.data.e_identifier.ref); + if (strings.eqlComptime(left_left_name, "Object") and strings.eqlComptime(left.data.e_dot.name, "defineProperty")) { + break :brk try p.parseCallArgsForObjectDefineProperty(); + } + } + + break :brk try p.parseCallArgs(); + }; + left = p.newExpr( E.Call{ .target = left, |