insert enumerable: true when needed

This commit is contained in:
Dylan Conway
2023-08-31 22:24:34 -07:00
parent 7528ea0084
commit 7dd06e2a12
2 changed files with 120 additions and 11 deletions

View File

@@ -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);

View File

@@ -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,