mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 08:57:34 +00:00
LibJS: Implement RegExp legacy static properties
RegExp legacy static properties Spec url is https://github.com/tc39/proposal-regexp-legacy-features
This commit is contained in:
parent
5e2fe7e2bf
commit
0d96468e9b
12 changed files with 726 additions and 9 deletions
|
@ -3510,7 +3510,14 @@ Completion RegExpLiteral::execute(Interpreter& interpreter) const
|
|||
|
||||
// 3. Return ! RegExpCreate(pattern, flags).
|
||||
Regex<ECMA262> regex(parsed_regex(), parsed_pattern(), parsed_flags());
|
||||
return Value { RegExpObject::create(realm, move(regex), move(pattern), move(flags)) };
|
||||
// NOTE: We bypass RegExpCreate and subsequently RegExpAlloc as an optimization to use the already parsed values.
|
||||
auto* regexp_object = RegExpObject::create(realm, move(regex), move(pattern), move(flags));
|
||||
// RegExpAlloc has these two steps from the 'Legacy RegExp features' proposal.
|
||||
regexp_object->set_realm(*vm.current_realm());
|
||||
// We don't need to check 'If SameValue(newTarget, thisRealm.[[Intrinsics]].[[%RegExp%]]) is true'
|
||||
// here as we know RegExpCreate calls RegExpAlloc with %RegExp% for newTarget.
|
||||
regexp_object->set_legacy_features_enabled(true);
|
||||
return Value { regexp_object };
|
||||
}
|
||||
|
||||
void ArrayExpression::dump(int indent) const
|
||||
|
|
|
@ -169,6 +169,7 @@ set(SOURCES
|
|||
Runtime/Reference.cpp
|
||||
Runtime/ReflectObject.cpp
|
||||
Runtime/RegExpConstructor.cpp
|
||||
Runtime/RegExpLegacyStaticProperties.cpp
|
||||
Runtime/RegExpObject.cpp
|
||||
Runtime/RegExpPrototype.cpp
|
||||
Runtime/RegExpStringIterator.cpp
|
||||
|
|
|
@ -18,6 +18,15 @@ namespace JS {
|
|||
P(__defineSetter__) \
|
||||
P(__lookupGetter__) \
|
||||
P(__lookupSetter__) \
|
||||
P($1) \
|
||||
P($2) \
|
||||
P($3) \
|
||||
P($4) \
|
||||
P($5) \
|
||||
P($6) \
|
||||
P($7) \
|
||||
P($8) \
|
||||
P($9) \
|
||||
P(Atomics) \
|
||||
P(BYTES_PER_ELEMENT) \
|
||||
P(BigInt) \
|
||||
|
@ -319,6 +328,9 @@ namespace JS {
|
|||
P(log1p) \
|
||||
P(log2) \
|
||||
P(log10) \
|
||||
P(lastMatch) \
|
||||
P(lastParen) \
|
||||
P(leftContext) \
|
||||
P(map) \
|
||||
P(max) \
|
||||
P(maximize) \
|
||||
|
@ -403,6 +415,7 @@ namespace JS {
|
|||
P(roundingIncrement) \
|
||||
P(roundingMode) \
|
||||
P(roundingPriority) \
|
||||
P(rightContext) \
|
||||
P(script) \
|
||||
P(seal) \
|
||||
P(second) \
|
||||
|
@ -567,6 +580,11 @@ struct CommonPropertyNames {
|
|||
PropertyKey return_ { "return", PropertyKey::StringMayBeNumber::No };
|
||||
PropertyKey throw_ { "throw", PropertyKey::StringMayBeNumber::No };
|
||||
PropertyKey xor_ { "xor", PropertyKey::StringMayBeNumber::No };
|
||||
PropertyKey inputAlias { "$_", PropertyKey::StringMayBeNumber::No };
|
||||
PropertyKey lastMatchAlias { "$&", PropertyKey::StringMayBeNumber::No };
|
||||
PropertyKey lastParenAlias { "$+", PropertyKey::StringMayBeNumber::No };
|
||||
PropertyKey leftContextAlias { "$`", PropertyKey::StringMayBeNumber::No };
|
||||
PropertyKey rightContextAlias { "$'", PropertyKey::StringMayBeNumber::No };
|
||||
#define __ENUMERATE(x) PropertyKey x { #x, PropertyKey::StringMayBeNumber::No };
|
||||
ENUMERATE_STANDARD_PROPERTY_NAMES(__ENUMERATE)
|
||||
#undef __ENUMERATE
|
||||
|
|
|
@ -304,7 +304,12 @@
|
|||
M(BadArgCountMany, "{}() needs {} arguments") \
|
||||
M(NotEnoughMemoryToAllocate, "Not enough memory to allocate {} bytes") \
|
||||
M(InvalidEnumerationValue, "Invalid value '{}' for enumeration type '{}'") \
|
||||
M(InvalidNormalizationForm, "The normalization form must be one of NFC, NFD, NFKC, NFKD. Got '{}'")
|
||||
M(InvalidNormalizationForm, "The normalization form must be one of NFC, NFD, NFKC, NFKD. Got '{}'") \
|
||||
M(SetLegacyRegExpStaticPropertyThisValueMismatch, "Legacy RegExp static property setter " \
|
||||
"must be called with the RegExp constructor for the this value") \
|
||||
M(GetLegacyRegExpStaticPropertyThisValueMismatch, "Legacy RegExp static property getter " \
|
||||
"must be called with the RegExp constructor for the this value") \
|
||||
M(GetLegacyRegExpStaticPropertyValueEmpty, "Legacy RegExp static property getter value is empty")
|
||||
|
||||
namespace JS {
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/RegExpConstructor.h>
|
||||
#include <LibJS/Runtime/RegExpObject.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
|
@ -27,6 +28,27 @@ void RegExpConstructor::initialize(Realm& realm)
|
|||
define_native_accessor(realm, *vm.well_known_symbol_species(), symbol_species_getter, {}, Attribute::Configurable);
|
||||
|
||||
define_direct_property(vm.names.length, Value(2), Attribute::Configurable);
|
||||
|
||||
// Additional properties of the RegExp constructor, https://github.com/tc39/proposal-regexp-legacy-features#additional-properties-of-the-regexp-constructor
|
||||
define_native_accessor(realm, vm.names.input, input_getter, input_setter, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.inputAlias, input_alias_getter, input_alias_setter, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.lastMatch, last_match_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.lastMatchAlias, last_match_alias_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.lastParen, last_paren_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.lastParenAlias, last_paren_alias_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.leftContext, left_context_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.leftContextAlias, left_context_alias_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.rightContext, right_context_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.rightContextAlias, right_context_alias_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.$1, group_1_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.$2, group_2_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.$3, group_3_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.$4, group_4_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.$5, group_5_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.$6, group_6_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.$7, group_7_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.$8, group_8_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.$9, group_9_getter, {}, Attribute::Configurable);
|
||||
}
|
||||
|
||||
// 22.2.3.1 RegExp ( pattern, flags ), https://tc39.es/ecma262/#sec-regexp-pattern-flags
|
||||
|
@ -125,4 +147,138 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::symbol_species_getter)
|
|||
return vm.this_value();
|
||||
}
|
||||
|
||||
// get RegExp.input, https://github.com/tc39/proposal-regexp-legacy-features#get-regexpinput
|
||||
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::input_getter)
|
||||
{
|
||||
auto* regexp_constructor = vm.current_realm()->intrinsics().regexp_constructor();
|
||||
|
||||
// 1. Return ? GetLegacyRegExpStaticProperty(%RegExp%, this value, [[RegExpInput]]).
|
||||
auto property_getter = &RegExpLegacyStaticProperties::input;
|
||||
return TRY(get_legacy_regexp_static_property(vm, *regexp_constructor, vm.this_value(), property_getter));
|
||||
}
|
||||
|
||||
// get RegExp.$_, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp_
|
||||
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::input_alias_getter)
|
||||
{
|
||||
// Keep the same implementation with `get RegExp.input`
|
||||
return input_getter(vm);
|
||||
}
|
||||
|
||||
// set RegExp.input, https://github.com/tc39/proposal-regexp-legacy-features#set-regexpinput--val
|
||||
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::input_setter)
|
||||
{
|
||||
auto* regexp_constructor = vm.current_realm()->intrinsics().regexp_constructor();
|
||||
|
||||
// 1. Perform ? SetLegacyRegExpStaticProperty(%RegExp%, this value, [[RegExpInput]], val).
|
||||
auto property_setter = &RegExpLegacyStaticProperties::set_input;
|
||||
TRY(set_legacy_regexp_static_property(vm, *regexp_constructor, vm.this_value(), property_setter, vm.argument(0)));
|
||||
return js_undefined();
|
||||
}
|
||||
|
||||
// set RegExp.$_, https://github.com/tc39/proposal-regexp-legacy-features#set-regexp_---val
|
||||
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::input_alias_setter)
|
||||
{
|
||||
// Keep the same implementation with `set RegExp.input`
|
||||
return input_setter(vm);
|
||||
}
|
||||
|
||||
// get RegExp.lastMatch, https://github.com/tc39/proposal-regexp-legacy-features#get-regexplastmatch
|
||||
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::last_match_getter)
|
||||
{
|
||||
auto* regexp_constructor = vm.current_realm()->intrinsics().regexp_constructor();
|
||||
|
||||
// 1. Return ? GetLegacyRegExpStaticProperty(%RegExp%, this value, [[RegExpLastMatch]]).
|
||||
auto property_getter = &RegExpLegacyStaticProperties::last_match;
|
||||
return TRY(get_legacy_regexp_static_property(vm, *regexp_constructor, vm.this_value(), property_getter));
|
||||
}
|
||||
|
||||
// get RegExp.$&, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp
|
||||
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::last_match_alias_getter)
|
||||
{
|
||||
// Keep the same implementation with `get RegExp.lastMatch`
|
||||
return last_match_getter(vm);
|
||||
}
|
||||
|
||||
// get RegExp.lastParen, https://github.com/tc39/proposal-regexp-legacy-features#get-regexplastparen
|
||||
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::last_paren_getter)
|
||||
{
|
||||
auto* regexp_constructor = vm.current_realm()->intrinsics().regexp_constructor();
|
||||
|
||||
// 1. Return ? GetLegacyRegExpStaticProperty(%RegExp%, this value, [[RegExpLastParen]]).
|
||||
auto property_getter = &RegExpLegacyStaticProperties::last_paren;
|
||||
return TRY(get_legacy_regexp_static_property(vm, *regexp_constructor, vm.this_value(), property_getter));
|
||||
}
|
||||
|
||||
// get RegExp.$+, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp-1
|
||||
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::last_paren_alias_getter)
|
||||
{
|
||||
// Keep the same implementation with `get RegExp.lastParen`
|
||||
return last_paren_getter(vm);
|
||||
}
|
||||
|
||||
// get RegExp.leftContext, https://github.com/tc39/proposal-regexp-legacy-features#get-regexpleftcontext
|
||||
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::left_context_getter)
|
||||
{
|
||||
auto* regexp_constructor = vm.current_realm()->intrinsics().regexp_constructor();
|
||||
|
||||
// 1. Return ? GetLegacyRegExpStaticProperty(%RegExp%, this value, [[RegExpLeftContext]]).
|
||||
auto property_getter = &RegExpLegacyStaticProperties::left_context;
|
||||
return TRY(get_legacy_regexp_static_property(vm, *regexp_constructor, vm.this_value(), property_getter));
|
||||
}
|
||||
|
||||
// get RegExp.$`, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp-2
|
||||
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::left_context_alias_getter)
|
||||
{
|
||||
// Keep the same implementation with `get RegExp.leftContext`
|
||||
return left_context_getter(vm);
|
||||
}
|
||||
|
||||
// get RegExp.rightContext, https://github.com/tc39/proposal-regexp-legacy-features#get-regexprightcontext
|
||||
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::right_context_getter)
|
||||
{
|
||||
auto* regexp_constructor = vm.current_realm()->intrinsics().regexp_constructor();
|
||||
|
||||
// 1. Return ? GetLegacyRegExpStaticProperty(%RegExp%, this value, [[RegExpRightContext]]).
|
||||
auto property_getter = &RegExpLegacyStaticProperties::right_context;
|
||||
return TRY(get_legacy_regexp_static_property(vm, *regexp_constructor, vm.this_value(), property_getter));
|
||||
}
|
||||
|
||||
// get RegExp.$', https://github.com/tc39/proposal-regexp-legacy-features#get-regexp-3
|
||||
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::right_context_alias_getter)
|
||||
{
|
||||
// Keep the same implementation with `get RegExp.rightContext`
|
||||
return right_context_getter(vm);
|
||||
}
|
||||
|
||||
#define DEFINE_REGEXP_GROUP_GETTER(n) \
|
||||
JS_DEFINE_NATIVE_FUNCTION(RegExpConstructor::group_##n##_getter) \
|
||||
{ \
|
||||
auto* regexp_constructor = vm.current_realm()->intrinsics().regexp_constructor(); \
|
||||
\
|
||||
/* 1. Return ? GetLegacyRegExpStaticProperty(%RegExp%, this value, [[RegExpParen##n##]]).*/ \
|
||||
auto property_getter = &RegExpLegacyStaticProperties::$##n; \
|
||||
return TRY(get_legacy_regexp_static_property(vm, *regexp_constructor, vm.this_value(), property_getter)); \
|
||||
}
|
||||
|
||||
// get RegExp.$1, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp1
|
||||
DEFINE_REGEXP_GROUP_GETTER(1);
|
||||
// get RegExp.$2, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp2
|
||||
DEFINE_REGEXP_GROUP_GETTER(2);
|
||||
// get RegExp.$3, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp3
|
||||
DEFINE_REGEXP_GROUP_GETTER(3);
|
||||
// get RegExp.$4, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp4
|
||||
DEFINE_REGEXP_GROUP_GETTER(4);
|
||||
// get RegExp.$5, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp5
|
||||
DEFINE_REGEXP_GROUP_GETTER(5);
|
||||
// get RegExp.$6, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp6
|
||||
DEFINE_REGEXP_GROUP_GETTER(6);
|
||||
// get RegExp.$7, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp7
|
||||
DEFINE_REGEXP_GROUP_GETTER(7);
|
||||
// get RegExp.$8, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp8
|
||||
DEFINE_REGEXP_GROUP_GETTER(8);
|
||||
// get RegExp.$9, https://github.com/tc39/proposal-regexp-legacy-features#get-regexp9
|
||||
DEFINE_REGEXP_GROUP_GETTER(9);
|
||||
|
||||
#undef DEFINE_REGEXP_GROUP_GETTER
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
#include <LibJS/Runtime/RegExpLegacyStaticProperties.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
|
@ -20,12 +21,37 @@ public:
|
|||
virtual ThrowCompletionOr<Value> call() override;
|
||||
virtual ThrowCompletionOr<Object*> construct(FunctionObject& new_target) override;
|
||||
|
||||
RegExpLegacyStaticProperties& legacy_static_properties() { return m_legacy_static_properties; }
|
||||
|
||||
private:
|
||||
explicit RegExpConstructor(Realm&);
|
||||
|
||||
virtual bool has_constructor() const override { return true; }
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(symbol_species_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(input_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(input_alias_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(input_setter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(input_alias_setter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(last_match_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(last_match_alias_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(last_paren_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(last_paren_alias_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(left_context_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(left_context_alias_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(right_context_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(right_context_alias_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(group_1_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(group_2_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(group_3_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(group_4_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(group_5_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(group_6_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(group_7_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(group_8_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(group_9_getter);
|
||||
|
||||
RegExpLegacyStaticProperties m_legacy_static_properties;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright (c) 2022, LI YUBEI <leeight@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/RegExpConstructor.h>
|
||||
#include <LibJS/Runtime/RegExpLegacyStaticProperties.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
void RegExpLegacyStaticProperties::invalidate()
|
||||
{
|
||||
m_input = {};
|
||||
m_last_match = {};
|
||||
m_last_paren = {};
|
||||
m_left_context = {};
|
||||
m_right_context = {};
|
||||
m_$1 = {};
|
||||
m_$2 = {};
|
||||
m_$3 = {};
|
||||
m_$4 = {};
|
||||
m_$5 = {};
|
||||
m_$6 = {};
|
||||
m_$7 = {};
|
||||
m_$8 = {};
|
||||
m_$9 = {};
|
||||
}
|
||||
|
||||
// GetLegacyRegExpStaticProperty( C, thisValue, internalSlotName ), https://github.com/tc39/proposal-regexp-legacy-features#getlegacyregexpstaticproperty-c-thisvalue-internalslotname-
|
||||
ThrowCompletionOr<Value> get_legacy_regexp_static_property(VM& vm, RegExpConstructor& constructor, Value this_value, Optional<String> const& (RegExpLegacyStaticProperties::*property_getter)() const)
|
||||
{
|
||||
// 1. Assert C is an object that has an internal slot named internalSlotName.
|
||||
|
||||
// 2. If SameValue(C, thisValue) is false, throw a TypeError exception.
|
||||
if (!same_value(&constructor, this_value))
|
||||
return vm.throw_completion<TypeError>(ErrorType::GetLegacyRegExpStaticPropertyThisValueMismatch);
|
||||
|
||||
// 3. Let val be the value of the internal slot of C named internalSlotName.
|
||||
auto val = (constructor.legacy_static_properties().*property_getter)();
|
||||
|
||||
// 4. If val is empty, throw a TypeError exception.
|
||||
if (!val.has_value())
|
||||
return vm.throw_completion<TypeError>(ErrorType::GetLegacyRegExpStaticPropertyValueEmpty);
|
||||
|
||||
// 5. Return val.
|
||||
return js_string(vm, val.release_value());
|
||||
}
|
||||
|
||||
// SetLegacyRegExpStaticProperty( C, thisValue, internalSlotName, val ), https://github.com/tc39/proposal-regexp-legacy-features#setlegacyregexpstaticproperty-c-thisvalue-internalslotname-val-
|
||||
ThrowCompletionOr<void> set_legacy_regexp_static_property(VM& vm, RegExpConstructor& constructor, Value this_value, void (RegExpLegacyStaticProperties::*property_setter)(String), Value value)
|
||||
{
|
||||
// 1. Assert C is an object that has an internal slot named internalSlotName.
|
||||
|
||||
// 2. If SameValue(C, thisValue) is false, throw a TypeError exception.
|
||||
if (!same_value(&constructor, this_value))
|
||||
return vm.throw_completion<TypeError>(ErrorType::SetLegacyRegExpStaticPropertyThisValueMismatch);
|
||||
|
||||
// 3. Let strVal be ? ToString(val).
|
||||
auto str_value = TRY(value.to_string(vm));
|
||||
|
||||
// 4. Set the value of the internal slot of C named internalSlotName to strVal.
|
||||
(constructor.legacy_static_properties().*property_setter)(str_value);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// UpdateLegacyRegExpStaticProperties ( C, S, startIndex, endIndex, capturedValues ), https://github.com/tc39/proposal-regexp-legacy-features#updatelegacyregexpstaticproperties--c-s-startindex-endindex-capturedvalues-
|
||||
void update_legacy_regexp_static_properties(RegExpConstructor& constructor, Utf16String const& string, size_t start_index, size_t end_index, Vector<String> const& captured_values)
|
||||
{
|
||||
auto& legacy_static_properties = constructor.legacy_static_properties();
|
||||
|
||||
// 1. Assert: C is an Object that has a [[RegExpInput]] internal slot.
|
||||
// 2. Assert: Type(S) is String.
|
||||
|
||||
// 3. Let len be the number of code units in S.
|
||||
auto len = string.length_in_code_units();
|
||||
|
||||
// 4. Assert: startIndex and endIndex are integers such that 0 ≤ startIndex ≤ endIndex ≤ len.
|
||||
VERIFY(start_index <= end_index);
|
||||
VERIFY(end_index <= len);
|
||||
|
||||
// 5. Assert: capturedValues is a List of Strings.
|
||||
|
||||
// 6. Let n be the number of elements in capturedValues.
|
||||
auto group_count = captured_values.size();
|
||||
|
||||
// 7. Set the value of C’s [[RegExpInput]] internal slot to S.
|
||||
legacy_static_properties.set_input(string.to_utf8());
|
||||
|
||||
// 8. Set the value of C’s [[RegExpLastMatch]] internal slot to a String whose length is endIndex - startIndex and containing the code units from S with indices startIndex through endIndex - 1, in ascending order.
|
||||
auto last_match = string.view().substring_view(start_index, end_index - start_index);
|
||||
legacy_static_properties.set_last_match(last_match.to_utf8());
|
||||
|
||||
// 9. If n > 0, set the value of C’s [[RegExpLastParen]] internal slot to the last element of capturedValues.
|
||||
if (group_count > 0) {
|
||||
auto item = captured_values[group_count - 1];
|
||||
legacy_static_properties.set_last_paren(item);
|
||||
}
|
||||
// 10. Else, set the value of C’s [[RegExpLastParen]] internal slot to the empty String.
|
||||
else {
|
||||
legacy_static_properties.set_last_paren(String::empty());
|
||||
}
|
||||
|
||||
// 11. Set the value of C’s [[RegExpLeftContext]] internal slot to a String whose length is startIndex and containing the code units from S with indices 0 through startIndex - 1, in ascending order.
|
||||
auto left_context = string.view().substring_view(0, start_index);
|
||||
legacy_static_properties.set_left_context(left_context.to_utf8());
|
||||
|
||||
// 12. Set the value of C’s [[RegExpRightContext]] internal slot to a String whose length is len - endIndex and containing the code units from S with indices endIndex through len - 1, in ascending order.
|
||||
auto right_context = string.view().substring_view(end_index, len - end_index);
|
||||
legacy_static_properties.set_right_context(right_context.to_utf8());
|
||||
|
||||
// 13. For each integer i such that 1 ≤ i ≤ 9
|
||||
for (size_t i = 1; i <= 9; i++) {
|
||||
auto value = String::empty();
|
||||
// If i ≤ n, set the value of C’s [[RegExpPareni]] internal slot to the ith element of capturedValues.
|
||||
if (i <= group_count) {
|
||||
value = captured_values[i - 1];
|
||||
}
|
||||
// Else, set the value of C’s [[RegExpPareni]] internal slot to the empty String.
|
||||
else {
|
||||
// It's already an empty string
|
||||
}
|
||||
|
||||
if (i == 1) {
|
||||
legacy_static_properties.set_$1(value);
|
||||
} else if (i == 2) {
|
||||
legacy_static_properties.set_$2(value);
|
||||
} else if (i == 3) {
|
||||
legacy_static_properties.set_$3(value);
|
||||
} else if (i == 4) {
|
||||
legacy_static_properties.set_$4(value);
|
||||
} else if (i == 5) {
|
||||
legacy_static_properties.set_$5(value);
|
||||
} else if (i == 6) {
|
||||
legacy_static_properties.set_$6(value);
|
||||
} else if (i == 7) {
|
||||
legacy_static_properties.set_$7(value);
|
||||
} else if (i == 8) {
|
||||
legacy_static_properties.set_$8(value);
|
||||
} else if (i == 9) {
|
||||
legacy_static_properties.set_$9(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InvalidateLegacyRegExpStaticProperties ( C ), https://github.com/tc39/proposal-regexp-legacy-features#invalidatelegacyregexpstaticproperties--c
|
||||
void invalidate_legacy_regexp_static_properties(RegExpConstructor& constructor)
|
||||
{
|
||||
// 1. Assert: C is an Object that has a [[RegExpInput]] internal slot.
|
||||
|
||||
// 2. Set the value of the following internal slots of C to empty:
|
||||
constructor.legacy_static_properties().invalidate();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2022, LI YUBEI <leeight@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibJS/Forward.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
// https://github.com/tc39/proposal-regexp-legacy-features#regexp
|
||||
// The %RegExp% instrinsic object, which is the builtin RegExp constructor, has the following additional internal slots:
|
||||
// [[RegExpInput]]
|
||||
// [[RegExpLastMatch]]
|
||||
// [[RegExpLastParen]]
|
||||
// [[RegExpLeftContext]]
|
||||
// [[RegExpRightContext]]
|
||||
// [[RegExpParen1]] ... [[RegExpParen9]]
|
||||
class RegExpLegacyStaticProperties {
|
||||
public:
|
||||
Optional<String> const& input() const { return m_input; }
|
||||
Optional<String> const& last_match() const { return m_last_match; }
|
||||
Optional<String> const& last_paren() const { return m_last_paren; }
|
||||
Optional<String> const& left_context() const { return m_left_context; }
|
||||
Optional<String> const& right_context() const { return m_right_context; }
|
||||
Optional<String> const& $1() const { return m_$1; }
|
||||
Optional<String> const& $2() const { return m_$2; }
|
||||
Optional<String> const& $3() const { return m_$3; }
|
||||
Optional<String> const& $4() const { return m_$4; }
|
||||
Optional<String> const& $5() const { return m_$5; }
|
||||
Optional<String> const& $6() const { return m_$6; }
|
||||
Optional<String> const& $7() const { return m_$7; }
|
||||
Optional<String> const& $8() const { return m_$8; }
|
||||
Optional<String> const& $9() const { return m_$9; }
|
||||
|
||||
void set_input(String input) { m_input = move(input); }
|
||||
void set_last_match(String last_match) { m_last_match = move(last_match); }
|
||||
void set_last_paren(String last_paren) { m_last_paren = move(last_paren); }
|
||||
void set_left_context(String left_context) { m_left_context = move(left_context); }
|
||||
void set_right_context(String right_context) { m_right_context = move(right_context); }
|
||||
void set_$1(String value) { m_$1 = move(value); }
|
||||
void set_$2(String value) { m_$2 = move(value); }
|
||||
void set_$3(String value) { m_$3 = move(value); }
|
||||
void set_$4(String value) { m_$4 = move(value); }
|
||||
void set_$5(String value) { m_$5 = move(value); }
|
||||
void set_$6(String value) { m_$6 = move(value); }
|
||||
void set_$7(String value) { m_$7 = move(value); }
|
||||
void set_$8(String value) { m_$8 = move(value); }
|
||||
void set_$9(String value) { m_$9 = move(value); }
|
||||
void invalidate();
|
||||
|
||||
private:
|
||||
Optional<String> m_input;
|
||||
Optional<String> m_last_match;
|
||||
Optional<String> m_last_paren;
|
||||
Optional<String> m_left_context;
|
||||
Optional<String> m_right_context;
|
||||
Optional<String> m_$1;
|
||||
Optional<String> m_$2;
|
||||
Optional<String> m_$3;
|
||||
Optional<String> m_$4;
|
||||
Optional<String> m_$5;
|
||||
Optional<String> m_$6;
|
||||
Optional<String> m_$7;
|
||||
Optional<String> m_$8;
|
||||
Optional<String> m_$9;
|
||||
};
|
||||
|
||||
ThrowCompletionOr<void> set_legacy_regexp_static_property(VM& vm, RegExpConstructor& constructor, Value this_value, void (RegExpLegacyStaticProperties::*property_setter)(String), Value value);
|
||||
ThrowCompletionOr<Value> get_legacy_regexp_static_property(VM& vm, RegExpConstructor& constructor, Value this_value, Optional<String> const& (RegExpLegacyStaticProperties::*property_getter)() const);
|
||||
void update_legacy_regexp_static_properties(RegExpConstructor& constructor, Utf16String const& string, size_t start_index, size_t end_index, Vector<String> const& captured_values);
|
||||
void invalidate_legacy_regexp_static_properties(RegExpConstructor& constructor);
|
||||
|
||||
}
|
|
@ -152,6 +152,7 @@ void RegExpObject::initialize(Realm& realm)
|
|||
{
|
||||
auto& vm = this->vm();
|
||||
Object::initialize(realm);
|
||||
|
||||
define_direct_property(vm.names.lastIndex, Value(0), Attribute::Writable);
|
||||
}
|
||||
|
||||
|
@ -258,15 +259,34 @@ ThrowCompletionOr<NonnullGCPtr<RegExpObject>> regexp_create(VM& vm, Value patter
|
|||
}
|
||||
|
||||
// 22.2.3.2 RegExpAlloc ( newTarget ), https://tc39.es/ecma262/#sec-regexpalloc
|
||||
// 22.2.3.2 RegExpAlloc ( newTarget ), https://github.com/tc39/proposal-regexp-legacy-features#regexpalloc--newtarget-
|
||||
ThrowCompletionOr<NonnullGCPtr<RegExpObject>> regexp_alloc(VM& vm, FunctionObject& new_target)
|
||||
{
|
||||
// 1. Let obj be ? OrdinaryCreateFromConstructor(newTarget, "%RegExp.prototype%", « [[OriginalSource]], [[OriginalFlags]], [[RegExpRecord]], [[RegExpMatcher]] »).
|
||||
auto* regexp_object = TRY(ordinary_create_from_constructor<RegExpObject>(vm, new_target, &Intrinsics::regexp_prototype));
|
||||
|
||||
// 2. Perform ! DefinePropertyOrThrow(obj, "lastIndex", PropertyDescriptor { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
|
||||
// 2. Let thisRealm be the current Realm Record.
|
||||
auto& this_realm = *vm.current_realm();
|
||||
|
||||
// 3. Set the value of obj’s [[Realm]] internal slot to thisRealm.
|
||||
regexp_object->set_realm(this_realm);
|
||||
|
||||
// 4. If SameValue(newTarget, thisRealm.[[Intrinsics]].[[%RegExp%]]) is true, then
|
||||
auto* regexp_constructor = this_realm.intrinsics().regexp_constructor();
|
||||
if (same_value(&new_target, regexp_constructor)) {
|
||||
// i. Set the value of obj’s [[LegacyFeaturesEnabled]] internal slot to true.
|
||||
regexp_object->set_legacy_features_enabled(true);
|
||||
}
|
||||
// 5. Else,
|
||||
else {
|
||||
// i. Set the value of obj’s [[LegacyFeaturesEnabled]] internal slot to false.
|
||||
regexp_object->set_legacy_features_enabled(false);
|
||||
}
|
||||
|
||||
// 6. Perform ! DefinePropertyOrThrow(obj, "lastIndex", PropertyDescriptor { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
|
||||
MUST(regexp_object->define_property_or_throw(vm.names.lastIndex, PropertyDescriptor { .writable = true, .enumerable = false, .configurable = false }));
|
||||
|
||||
// 3. Return obj.
|
||||
// 7. Return obj.
|
||||
return NonnullGCPtr { *regexp_object };
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,11 @@ public:
|
|||
String const& flags() const { return m_flags; }
|
||||
Regex<ECMA262> const& regex() { return *m_regex; }
|
||||
Regex<ECMA262> const& regex() const { return *m_regex; }
|
||||
Realm& realm() { return *m_realm; }
|
||||
Realm const& realm() const { return *m_realm; }
|
||||
bool legacy_features_enabled() const { return m_legacy_features_enabled; }
|
||||
void set_legacy_features_enabled(bool legacy_features_enabled) { m_legacy_features_enabled = legacy_features_enabled; }
|
||||
void set_realm(Realm& realm) { m_realm = &realm; }
|
||||
|
||||
private:
|
||||
RegExpObject(Object& prototype);
|
||||
|
@ -57,6 +62,9 @@ private:
|
|||
|
||||
String m_pattern;
|
||||
String m_flags;
|
||||
bool m_legacy_features_enabled { false }; // [[LegacyFeaturesEnabled]]
|
||||
// Note: This is initialized in RegExpAlloc, but will be non-null afterwards
|
||||
GCPtr<Realm> m_realm; // [[Realm]]
|
||||
Optional<Regex<ECMA262>> m_regex;
|
||||
};
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
#include <LibJS/Runtime/Error.h>
|
||||
#include <LibJS/Runtime/ErrorTypes.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/RegExpConstructor.h>
|
||||
#include <LibJS/Runtime/RegExpObject.h>
|
||||
|
@ -163,6 +164,7 @@ static Value make_match_indices_index_pair_array(VM& vm, Utf16View const& string
|
|||
}
|
||||
|
||||
// 22.2.5.2.2 RegExpBuiltinExec ( R, S ), https://tc39.es/ecma262/#sec-regexpbuiltinexec
|
||||
// 22.2.5.2.2 RegExpBuiltInExec ( R, S ), https://github.com/tc39/proposal-regexp-legacy-features#regexpbuiltinexec--r-s-
|
||||
static ThrowCompletionOr<Value> regexp_builtin_exec(VM& vm, RegExpObject& regexp_object, Utf16String string)
|
||||
{
|
||||
auto& realm = *vm.current_realm();
|
||||
|
@ -172,7 +174,7 @@ static ThrowCompletionOr<Value> regexp_builtin_exec(VM& vm, RegExpObject& regexp
|
|||
auto last_index_value = TRY(regexp_object.get(vm.names.lastIndex));
|
||||
auto last_index = TRY(last_index_value.to_length(vm));
|
||||
|
||||
auto& regex = regexp_object.regex();
|
||||
auto const& regex = regexp_object.regex();
|
||||
|
||||
// 3. Let flags be R.[[OriginalFlags]].
|
||||
// 4. If flags contains "g", let global be true; else let global be false.
|
||||
|
@ -263,6 +265,7 @@ static ThrowCompletionOr<Value> regexp_builtin_exec(VM& vm, RegExpObject& regexp
|
|||
|
||||
// 24. Let indices be a new empty List.
|
||||
Vector<Optional<Match>> indices;
|
||||
Vector<String> captured_values;
|
||||
|
||||
// 25. Let groupNames be a new empty List.
|
||||
HashMap<FlyString, Match> group_names;
|
||||
|
@ -296,6 +299,8 @@ static ThrowCompletionOr<Value> regexp_builtin_exec(VM& vm, RegExpObject& regexp
|
|||
captured_value = js_undefined();
|
||||
// ii. Append undefined to indices.
|
||||
indices.append({});
|
||||
// iii. Append capture to indices.
|
||||
captured_values.append(String::empty());
|
||||
}
|
||||
// c. Else,
|
||||
else {
|
||||
|
@ -307,8 +312,10 @@ static ThrowCompletionOr<Value> regexp_builtin_exec(VM& vm, RegExpObject& regexp
|
|||
// iv. Let capture be the Match { [[StartIndex]]: captureStart, [[EndIndex]: captureEnd }.
|
||||
// v. Let capturedValue be ! GetMatchString(S, capture).
|
||||
captured_value = js_string(vm, capture.view.u16_view());
|
||||
// vi Append capture to indices.
|
||||
// vi. Append capture to indices.
|
||||
indices.append(Match::create(capture));
|
||||
// vii. Append capturedValue to the end of capturedValues.
|
||||
captured_values.append(capture.view.to_string());
|
||||
}
|
||||
|
||||
// d. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(i)), capturedValue).
|
||||
|
@ -332,6 +339,27 @@ static ThrowCompletionOr<Value> regexp_builtin_exec(VM& vm, RegExpObject& regexp
|
|||
}
|
||||
}
|
||||
|
||||
// https://github.com/tc39/proposal-regexp-legacy-features#regexpbuiltinexec--r-s-
|
||||
// 5. Let thisRealm be the current Realm Record.
|
||||
auto* this_realm = &realm;
|
||||
// 6. Let rRealm be the value of R's [[Realm]] internal slot.
|
||||
auto* regexp_object_realm = ®exp_object.realm();
|
||||
// 7. If SameValue(thisRealm, rRealm) is true, then
|
||||
if (this_realm == regexp_object_realm) {
|
||||
// i. If the value of R’s [[LegacyFeaturesEnabled]] internal slot is true, then
|
||||
if (regexp_object.legacy_features_enabled()) {
|
||||
// a. Perform UpdateLegacyRegExpStaticProperties(%RegExp%, S, lastIndex, e, capturedValues).
|
||||
auto* regexp_constructor = realm.intrinsics().regexp_constructor();
|
||||
update_legacy_regexp_static_properties(*regexp_constructor, string, match_indices.start_index, match_indices.end_index, captured_values);
|
||||
}
|
||||
// ii. Else,
|
||||
else {
|
||||
// a. Perform InvalidateLegacyRegExpStaticProperties(%RegExp%).
|
||||
auto* regexp_constructor = realm.intrinsics().regexp_constructor();
|
||||
invalidate_legacy_regexp_static_properties(*regexp_constructor);
|
||||
}
|
||||
}
|
||||
|
||||
// 31. Perform ! CreateDataPropertyOrThrow(A, "groups", groups).
|
||||
// NOTE: This step must be performed after the above loop in order for groups to be populated.
|
||||
Value groups = has_groups ? groups_object : js_undefined();
|
||||
|
@ -1060,6 +1088,7 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::to_string)
|
|||
}
|
||||
|
||||
// B.2.4.1 RegExp.prototype.compile ( pattern, flags ), https://tc39.es/ecma262/#sec-regexp.prototype.compile
|
||||
// B.2.4.1 RegExp.prototype.compile ( pattern, flags ), https://github.com/tc39/proposal-regexp-legacy-features#regexpprototypecompile--pattern-flags-
|
||||
JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::compile)
|
||||
{
|
||||
auto pattern = vm.argument(0);
|
||||
|
@ -1069,7 +1098,21 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::compile)
|
|||
// 2. Perform ? RequireInternalSlot(O, [[RegExpMatcher]]).
|
||||
auto* regexp_object = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. If Type(pattern) is Object and pattern has a [[RegExpMatcher]] internal slot, then
|
||||
// 3. Let thisRealm be the current Realm Record.
|
||||
auto* this_realm = vm.current_realm();
|
||||
|
||||
// 4. Let oRealm be the value of O’s [[Realm]] internal slot.
|
||||
auto* regexp_object_realm = ®exp_object->realm();
|
||||
|
||||
// 5. If SameValue(thisRealm, oRealm) is false, throw a TypeError exception.
|
||||
if (this_realm != regexp_object_realm)
|
||||
return vm.throw_completion<TypeError>(ErrorType::RegExpCompileError, "thisRealm and oRealm is not same value");
|
||||
|
||||
// 6. If the value of R’s [[LegacyFeaturesEnabled]] internal slot is false, throw a TypeError exception.
|
||||
if (!regexp_object->legacy_features_enabled())
|
||||
return vm.throw_completion<TypeError>(ErrorType::RegExpCompileError, "legacy features is not enabled");
|
||||
|
||||
// 7. If Type(pattern) is Object and pattern has a [[RegExpMatcher]] internal slot, then
|
||||
if (pattern.is_object() && is<RegExpObject>(pattern.as_object())) {
|
||||
// a. If flags is not undefined, throw a TypeError exception.
|
||||
if (!flags.is_undefined())
|
||||
|
@ -1083,11 +1126,11 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::compile)
|
|||
// c. Let F be pattern.[[OriginalFlags]].
|
||||
flags = js_string(vm, regexp_pattern.flags());
|
||||
}
|
||||
// 4. Else,
|
||||
// 8. Else,
|
||||
// a. Let P be pattern.
|
||||
// b. Let F be flags.
|
||||
|
||||
// 5. Return ? RegExpInitialize(O, P, F).
|
||||
// 9. Return ? RegExpInitialize(O, P, F).
|
||||
return TRY(regexp_object->regexp_initialize(vm, pattern, flags));
|
||||
}
|
||||
|
||||
|
|
198
Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.legacy.js
Normal file
198
Userland/Libraries/LibJS/Tests/builtins/RegExp/RegExp.legacy.js
Normal file
|
@ -0,0 +1,198 @@
|
|||
// NOTE: This file came from https://github.com/v8/v8/blob/main/test/mjsunit/regexp-static.js
|
||||
// and was modified to match LibJS test framework.
|
||||
|
||||
test("basic functionality", () => {
|
||||
var re = /((\d+)\.(\d+))/;
|
||||
var s = "abc123.456def";
|
||||
re.exec(s);
|
||||
|
||||
expect(RegExp.input).toBe(s);
|
||||
expect(RegExp["$_"]).toBe(s);
|
||||
expect(RegExp.lastMatch).toBe("123.456");
|
||||
expect(RegExp["$&"]).toBe("123.456");
|
||||
expect(RegExp.lastParen).toBe("456");
|
||||
expect(RegExp["$+"]).toBe("456");
|
||||
expect(RegExp.leftContext).toBe("abc");
|
||||
expect(RegExp["$`"]).toBe("abc");
|
||||
expect(RegExp.rightContext).toBe("def");
|
||||
expect(RegExp["$'"]).toBe("def");
|
||||
|
||||
expect(RegExp.$1).toBe("123.456");
|
||||
expect(RegExp.$2).toBe("123");
|
||||
expect(RegExp.$3).toBe("456");
|
||||
expect(RegExp.$4).toBe("");
|
||||
expect(RegExp.$5).toBe("");
|
||||
expect(RegExp.$6).toBe("");
|
||||
expect(RegExp.$7).toBe("");
|
||||
expect(RegExp.$8).toBe("");
|
||||
expect(RegExp.$9).toBe("");
|
||||
});
|
||||
|
||||
test("They should be read only", () => {
|
||||
RegExp["$1"] = "fisk";
|
||||
expect(RegExp.$1).toBe("123.456");
|
||||
|
||||
RegExp.input = Number();
|
||||
expect(typeof RegExp.input).toBe(typeof String());
|
||||
});
|
||||
|
||||
test("They should be read only (strict mode)", () => {
|
||||
"use strict";
|
||||
|
||||
try {
|
||||
RegExp["$1"] = "fisk";
|
||||
expect(true).toBeFalse(); // SHOULD NOT REACH HERE
|
||||
} catch (e) {
|
||||
expect(String(e)).toBe("TypeError: Cannot set property '$1' of [object RegExpConstructor]");
|
||||
expect(RegExp.$1).toBe("123.456");
|
||||
}
|
||||
|
||||
RegExp.input = Number();
|
||||
expect(typeof RegExp.input).toBe(typeof String());
|
||||
});
|
||||
|
||||
test("the original accessor vs the alias accessor", () => {
|
||||
expect(Object.getOwnPropertyDescriptor(RegExp, "input").get).not.toBe(
|
||||
Object.getOwnPropertyDescriptor(RegExp, "$_").get
|
||||
);
|
||||
expect(Object.getOwnPropertyDescriptor(RegExp, "input").set).not.toBe(
|
||||
Object.getOwnPropertyDescriptor(RegExp, "$_").set
|
||||
);
|
||||
expect(Object.getOwnPropertyDescriptor(RegExp, "lastMatch").get).not.toBe(
|
||||
Object.getOwnPropertyDescriptor(RegExp, "$&").get
|
||||
);
|
||||
expect(Object.getOwnPropertyDescriptor(RegExp, "lastParen").get).not.toBe(
|
||||
Object.getOwnPropertyDescriptor(RegExp, "$+").get
|
||||
);
|
||||
expect(Object.getOwnPropertyDescriptor(RegExp, "leftContext").get).not.toBe(
|
||||
Object.getOwnPropertyDescriptor(RegExp, "$`").get
|
||||
);
|
||||
expect(Object.getOwnPropertyDescriptor(RegExp, "rightContext").get).not.toBe(
|
||||
Object.getOwnPropertyDescriptor(RegExp, "$'").get
|
||||
);
|
||||
});
|
||||
|
||||
test("match should all behave as if exec were called.", () => {
|
||||
var re = /((\d+)\.(\d+))/;
|
||||
var s = "ghi789.012jkl";
|
||||
s.match(re);
|
||||
|
||||
expect(RegExp.input).toBe(s);
|
||||
expect(RegExp["$_"]).toBe(s);
|
||||
expect(RegExp.lastMatch).toBe("789.012");
|
||||
expect(RegExp["$&"]).toBe("789.012");
|
||||
expect(RegExp.lastParen).toBe("012");
|
||||
expect(RegExp["$+"]).toBe("012");
|
||||
expect(RegExp.leftContext).toBe("ghi");
|
||||
expect(RegExp["$`"]).toBe("ghi");
|
||||
expect(RegExp.rightContext).toBe("jkl");
|
||||
expect(RegExp["$'"]).toBe("jkl");
|
||||
|
||||
expect(RegExp.$1).toBe("789.012");
|
||||
expect(RegExp.$2).toBe("789");
|
||||
expect(RegExp.$3).toBe("012");
|
||||
expect(RegExp.$4).toBe("");
|
||||
expect(RegExp.$5).toBe("");
|
||||
expect(RegExp.$6).toBe("");
|
||||
expect(RegExp.$7).toBe("");
|
||||
expect(RegExp.$8).toBe("");
|
||||
expect(RegExp.$9).toBe("");
|
||||
});
|
||||
|
||||
test("replace should all behave as if exec were called.", () => {
|
||||
var re = /((\d+)\.(\d+))/;
|
||||
var s = "abc123.456def";
|
||||
s.replace(re, "whocares");
|
||||
|
||||
expect(RegExp.input).toBe(s);
|
||||
expect(RegExp["$_"]).toBe(s);
|
||||
expect(RegExp.lastMatch).toBe("123.456");
|
||||
expect(RegExp["$&"]).toBe("123.456");
|
||||
expect(RegExp.lastParen).toBe("456");
|
||||
expect(RegExp["$+"]).toBe("456");
|
||||
expect(RegExp.leftContext).toBe("abc");
|
||||
expect(RegExp["$`"]).toBe("abc");
|
||||
expect(RegExp.rightContext).toBe("def");
|
||||
expect(RegExp["$'"]).toBe("def");
|
||||
|
||||
expect(RegExp.$1).toBe("123.456");
|
||||
expect(RegExp.$2).toBe("123");
|
||||
expect(RegExp.$3).toBe("456");
|
||||
expect(RegExp.$4).toBe("");
|
||||
expect(RegExp.$5).toBe("");
|
||||
expect(RegExp.$6).toBe("");
|
||||
expect(RegExp.$7).toBe("");
|
||||
expect(RegExp.$8).toBe("");
|
||||
expect(RegExp.$9).toBe("");
|
||||
});
|
||||
|
||||
test("test should all behave as if exec were called.", () => {
|
||||
var re = /((\d+)\.(\d+))/;
|
||||
var s = "ghi789.012jkl";
|
||||
re.test(s);
|
||||
|
||||
expect(RegExp.input).toBe(s);
|
||||
expect(RegExp["$_"]).toBe(s);
|
||||
expect(RegExp.lastMatch).toBe("789.012");
|
||||
expect(RegExp["$&"]).toBe("789.012");
|
||||
expect(RegExp.lastParen).toBe("012");
|
||||
expect(RegExp["$+"]).toBe("012");
|
||||
expect(RegExp.leftContext).toBe("ghi");
|
||||
expect(RegExp["$`"]).toBe("ghi");
|
||||
expect(RegExp.rightContext).toBe("jkl");
|
||||
expect(RegExp["$'"]).toBe("jkl");
|
||||
|
||||
expect(RegExp.$1).toBe("789.012");
|
||||
expect(RegExp.$2).toBe("789");
|
||||
expect(RegExp.$3).toBe("012");
|
||||
expect(RegExp.$4).toBe("");
|
||||
expect(RegExp.$5).toBe("");
|
||||
expect(RegExp.$6).toBe("");
|
||||
expect(RegExp.$7).toBe("");
|
||||
expect(RegExp.$8).toBe("");
|
||||
expect(RegExp.$9).toBe("");
|
||||
});
|
||||
|
||||
test("replace must interleave matching and replacing when a global regexp is matched and replaced with the result of a function", () => {
|
||||
var re = /(.)/g;
|
||||
function f() {
|
||||
return RegExp.$1;
|
||||
}
|
||||
expect("abcd".replace(re, f)).toBe("dddd");
|
||||
});
|
||||
|
||||
test("the last parenthesis didn't match.", () => {
|
||||
expect(/foo(?:a(x))?/.exec("foobx")).toEqual(["foo", undefined]);
|
||||
expect(RegExp.lastParen).toBe("");
|
||||
});
|
||||
|
||||
test("$1 to $9", () => {
|
||||
for (var i = 1; i <= 9; i++) {
|
||||
var haystack = "foo";
|
||||
var re_text = "^foo";
|
||||
for (var j = 0; j < i - 1; j++) {
|
||||
haystack += "x";
|
||||
re_text += "(x)";
|
||||
}
|
||||
re_text += "(?:a(x))?";
|
||||
haystack += "bx";
|
||||
var re = new RegExp(re_text);
|
||||
expect(re.test(haystack)).toBeTrue();
|
||||
for (var j = 1; j < i - 1; j++) {
|
||||
expect(RegExp["$" + j]).toBe("x");
|
||||
}
|
||||
expect(RegExp["$" + i]).toBe("");
|
||||
}
|
||||
});
|
||||
|
||||
test("save the correct string as the last subject", () => {
|
||||
var foo =
|
||||
"lsdfj sldkfj sdklfj læsdfjl sdkfjlsdk fjsdl fjsdljskdj flsj flsdkj flskd regexp: /foobar/\nldkfj sdlkfj sdkl";
|
||||
expect(/^([a-z]+): (.*)/.test(foo.substring(foo.indexOf("regexp:")))).toBeTrue();
|
||||
expect(RegExp.$1).toBe("regexp");
|
||||
});
|
||||
|
||||
test("calling with no argument is the same as calling with undefined.", () => {
|
||||
expect(/^undefined$/.test()).toBeTrue();
|
||||
expect(/^undefined$/.exec()).toEqual(["undefined"]);
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue