From 9d6f00d918d26441d04f85795d3dbd3963977080 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Mon, 28 Aug 2023 23:40:30 +0200 Subject: [PATCH] LibJS: Behave like major engines when substituting missing capture group When a substitution refers to a 2-digit capture group that doesn't exist we need to check if the first digit refers to an existing capture group. In other words, '$10' should be treated as capture group #1, followed by the literal '0' if 1 is a valid capture group but 10 is not. This makes the Dromaeo "dom-query" subtest run to completion. --- Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp | 9 +++++++++ .../Tests/builtins/String/String.prototype.replace.js | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp index 12aee23a26..fc528e8f43 100644 --- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp @@ -1269,6 +1269,15 @@ ThrowCompletionOr get_substitution(VM& vm, Utf16View const& matched, Utf auto capture_position_string = TRY_OR_THROW_OOM(vm, replace_view.substring_view(i + 1, is_two_digits ? 2 : 1).to_utf8()); auto capture_position = capture_position_string.to_number(); + // NOTE: All major engines treat $10 as $1 followed by a literal '0' character + // if there are fewer than 10 capture groups. The spec disagrees. + // Spec bug: https://github.com/tc39/ecma262/issues/1426 + if (is_two_digits && capture_position.has_value() && (*capture_position > 0) && (*capture_position > captures.size())) { + is_two_digits = false; + capture_position_string = MUST(capture_position_string.substring_from_byte_offset(0, 1)); + capture_position = capture_position_string.to_number(); + } + if (capture_position.has_value() && (*capture_position > 0) && (*capture_position <= captures.size())) { auto& value = captures[*capture_position - 1]; diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.replace.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.replace.js index be6fe90f49..fb2706da1e 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.replace.js +++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.replace.js @@ -248,3 +248,9 @@ test("UTF-16", () => { expect("".replace("", "😀")).toBe("😀"); }); + +test("substitution with capture group", () => { + expect("A".replace(/(A)/, "$1")).toBe("A"); + expect("A".replace(/(A)/, "$10")).toBe("A0"); + expect("A".replace(/(A)/, "$2")).toBe("$2"); +});