From 3892b6e6ece8fd60ea592bf8c4f5674e99a544c8 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Thu, 8 Jul 2021 13:36:09 -0400 Subject: [PATCH] LibJS: Implement RegExp constructor according to the spec This allows passing an existing RegExp object (or an object that is sufficiently like a RegExp object) as the "pattern" argument of the RegExp constructor. --- .../LibJS/Runtime/RegExpConstructor.cpp | 59 ++++++++++++++++++- .../LibJS/Tests/builtins/RegExp/RegExp.js | 23 ++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/RegExpConstructor.cpp b/Userland/Libraries/LibJS/Runtime/RegExpConstructor.cpp index c5125b7cf34..bade8fb9ab5 100644 --- a/Userland/Libraries/LibJS/Runtime/RegExpConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/RegExpConstructor.cpp @@ -36,6 +36,25 @@ RegExpConstructor::~RegExpConstructor() // 22.2.3.1 RegExp ( pattern, flags ), https://tc39.es/ecma262/#sec-regexp-pattern-flags Value RegExpConstructor::call() { + auto& vm = this->vm(); + auto& global_object = this->global_object(); + + auto pattern = vm.argument(0); + auto flags = vm.argument(1); + + bool pattern_is_regexp = pattern.is_regexp(global_object); + if (vm.exception()) + return {}; + + if (pattern_is_regexp && flags.is_undefined()) { + auto pattern_constructor = pattern.as_object().get(vm.names.constructor); + if (vm.exception()) + return {}; + + if (same_value(this, pattern_constructor)) + return pattern; + } + return construct(*this); } @@ -43,8 +62,44 @@ Value RegExpConstructor::call() Value RegExpConstructor::construct(FunctionObject&) { auto& vm = this->vm(); - // FIXME: This is non-conforming - return regexp_create(global_object(), vm.argument(0), vm.argument(1)); + auto& global_object = this->global_object(); + + auto pattern = vm.argument(0); + auto flags = vm.argument(1); + + bool pattern_is_regexp = pattern.is_regexp(global_object); + if (vm.exception()) + return {}; + + Value pattern_value; + Value flags_value; + + if (pattern.is_object() && is(pattern.as_object())) { + auto& regexp_pattern = static_cast(pattern.as_object()); + pattern_value = js_string(vm, regexp_pattern.pattern()); + + if (flags.is_undefined()) + flags_value = js_string(vm, regexp_pattern.flags()); + else + flags_value = flags; + } else if (pattern_is_regexp) { + pattern_value = pattern.as_object().get(vm.names.source); + if (vm.exception()) + return {}; + + if (flags.is_undefined()) { + flags_value = pattern.as_object().get(vm.names.flags); + if (vm.exception()) + return {}; + } else { + flags_value = flags; + } + } else { + pattern_value = pattern; + flags_value = flags; + } + + return regexp_create(global_object, pattern_value, flags_value); } // 22.2.4.2 get RegExp [ @@species ], https://tc39.es/ecma262/#sec-get-regexp-@@species diff --git a/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.js b/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.js index 0a65d2a25bd..183d79284b2 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.js +++ b/Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.js @@ -29,3 +29,26 @@ test("basic functionality", () => { expect(RegExp("foo", "g").toString()).toBe("/foo/g"); expect(RegExp(undefined, "g").toString()).toBe("/(?:)/g"); }); + +test("regexp object as pattern parameter", () => { + expect(RegExp(/foo/).toString()).toBe("/foo/"); + expect(RegExp(/foo/g).toString()).toBe("/foo/g"); + expect(RegExp(/foo/g, "").toString()).toBe("/foo/"); + expect(RegExp(/foo/g, "y").toString()).toBe("/foo/y"); + + var regex_like_object_without_flags = { + source: "foo", + [Symbol.match]: function () {}, + }; + expect(RegExp(regex_like_object_without_flags).toString()).toBe("/foo/"); + expect(RegExp(regex_like_object_without_flags, "y").toString()).toBe("/foo/y"); + + var regex_like_object_with_flags = { + source: "foo", + flags: "g", + [Symbol.match]: function () {}, + }; + expect(RegExp(regex_like_object_with_flags).toString()).toBe("/foo/g"); + expect(RegExp(regex_like_object_with_flags, "").toString()).toBe("/foo/"); + expect(RegExp(regex_like_object_with_flags, "y").toString()).toBe("/foo/y"); +});