Browse Source

LibJS: Make RegExp.prototype.flags spec-compliant

This should be using the individual flag boolean properties rather than
the [[OriginalFlags]] internal slot.
Use an enumerator macro here for brevity, this will be useful for other
things as well. :^)
Linus Groh 4 years ago
parent
commit
ee66eaa1b0

+ 8 - 0
Libraries/LibJS/Forward.h

@@ -92,6 +92,14 @@
     __JS_ENUMERATE(toPrimitive, to_primitive)                \
     __JS_ENUMERATE(toStringTag, to_string_tag)
 
+#define JS_ENUMERATE_REGEXP_FLAGS                           \
+    __JS_ENUMERATE(global, global, g, Global)               \
+    __JS_ENUMERATE(ignoreCase, ignore_case, i, Insensitive) \
+    __JS_ENUMERATE(multiline, multiline, m, Multiline)      \
+    __JS_ENUMERATE(dotAll, dot_all, s, SingleLine)          \
+    __JS_ENUMERATE(unicode, unicode, u, Unicode)            \
+    __JS_ENUMERATE(sticky, sticky, y, Sticky)
+
 namespace JS {
 
 class ASTNode;

+ 20 - 15
Libraries/LibJS/Runtime/RegExpPrototype.cpp

@@ -62,6 +62,16 @@ RegExpPrototype::~RegExpPrototype()
 {
 }
 
+static Object* this_object_from(VM& vm, GlobalObject& global_object)
+{
+    auto this_value = vm.this_value(global_object);
+    if (!this_value.is_object()) {
+        vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, this_value.to_string_without_side_effects());
+        return {};
+    }
+    return &this_value.as_object();
+}
+
 static RegExpObject* regexp_object_from(VM& vm, GlobalObject& global_object)
 {
     auto* this_object = vm.this_value(global_object).to_object(global_object);
@@ -85,25 +95,20 @@ JS_DEFINE_NATIVE_GETTER(RegExpPrototype::dot_all)
 
 JS_DEFINE_NATIVE_GETTER(RegExpPrototype::flags)
 {
-    auto regexp_object = regexp_object_from(vm, global_object);
-    if (!regexp_object)
+    auto this_object = this_object_from(vm, global_object);
+    if (!this_object)
         return {};
 
-    auto flags = regexp_object->declared_options();
     StringBuilder builder(8);
 
-    if (flags.has_flag_set(ECMAScriptFlags::Global))
-        builder.append('g');
-    if (flags.has_flag_set(ECMAScriptFlags::Insensitive))
-        builder.append('i');
-    if (flags.has_flag_set(ECMAScriptFlags::Multiline))
-        builder.append('m');
-    if (flags.has_flag_set(ECMAScriptFlags::SingleLine))
-        builder.append('s');
-    if (flags.has_flag_set(ECMAScriptFlags::Unicode))
-        builder.append('u');
-    if (flags.has_flag_set(ECMAScriptFlags::Sticky))
-        builder.append('y');
+#define __JS_ENUMERATE(flagName, flag_name, flag_char, ECMAScriptFlagName)                \
+    auto flag_##flag_name = this_object->get(vm.names.flagName).value_or(js_undefined()); \
+    if (vm.exception())                                                                   \
+        return {};                                                                        \
+    if (flag_##flag_name.to_boolean())                                                    \
+        builder.append(#flag_char);
+    JS_ENUMERATE_REGEXP_FLAGS
+#undef __JS_ENUMERATE
 
     return js_string(vm, builder.to_string());
 }

+ 10 - 0
Libraries/LibJS/Tests/builtins/RegExp/RegExp.prototype.flags.js

@@ -0,0 +1,10 @@
+test("basic functionality", () => {
+    expect(/foo/.flags).toBe("");
+    expect(/foo/g.flags).toBe("g");
+    expect(/foo/i.flags).toBe("i");
+    expect(/foo/m.flags).toBe("m");
+    expect(/foo/s.flags).toBe("s");
+    expect(/foo/u.flags).toBe("u");
+    expect(/foo/y.flags).toBe("y");
+    expect(/foo/sgimyu.flags).toBe("gimsuy");
+});