Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 25 additions & 32 deletions Jint.Tests.Test262/Test262Harness.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"Parallel": true,
"ExcludedFeatures": [
"import-defer",
"regexp-lookbehind",
"regexp-modifiers",
"regexp-unicode-property-escapes",
"regexp-v-flag",
Expand All @@ -33,51 +32,45 @@
// Currently quite impossible to detect if assignment target is CoverParenthesizedExpression
"language/expressions/assignment/fn-name-lhs-cover.js",

// RegExp conversion limitations
// === REGEX EXCLUSIONS ===

// CharacterClassEscapes tests internally use v flag which is not supported
"built-ins/RegExp/CharacterClassEscapes/*.js",

// .NET regex engine: forward backreferences not supported in ECMAScript mode
"built-ins/RegExp/S15.10.2.11_A1_T5.js",
"built-ins/RegExp/S15.10.2.11_A1_T7.js",
"built-ins/RegExp/S15.10.2.5_A1_T4.js",
"language/literals/regexp/named-groups/forward-reference.js",
"built-ins/RegExp/named-groups/non-unicode-references.js",
"built-ins/RegExp/named-groups/unicode-references.js",
"built-ins/RegExp/quantifier-integer-limit.js",
"language/literals/regexp/named-groups/forward-reference.js",

"built-ins/RegExp/CharacterClassEscapes/character-class-digit-class-escape-negative-cases.js",
"built-ins/RegExp/CharacterClassEscapes/character-class-digit-class-escape-positive-cases.js",
"built-ins/RegExp/CharacterClassEscapes/character-class-non-digit-class-escape-negative-cases.js",
"built-ins/RegExp/CharacterClassEscapes/character-class-non-digit-class-escape-positive-cases.js",
"built-ins/RegExp/CharacterClassEscapes/character-class-non-whitespace-class-escape-negative-cases.js",
"built-ins/RegExp/CharacterClassEscapes/character-class-non-whitespace-class-escape-positive-cases.js",
"built-ins/RegExp/CharacterClassEscapes/character-class-non-word-class-escape-negative-cases.js",
"built-ins/RegExp/CharacterClassEscapes/character-class-non-word-class-escape-positive-cases.js",
"built-ins/RegExp/CharacterClassEscapes/character-class-whitespace-class-escape-negative-cases.js",
"built-ins/RegExp/CharacterClassEscapes/character-class-whitespace-class-escape-positive-cases.js",
"built-ins/RegExp/CharacterClassEscapes/character-class-word-class-escape-negative-cases.js",
"built-ins/RegExp/CharacterClassEscapes/character-class-word-class-escape-positive-cases.js",

// RegExp handling problems
// .NET regex engine: lookbehind backreference capture semantics differ from JS
"built-ins/RegExp/lookBehind/back-references-to-captures.js",
"built-ins/RegExp/lookBehind/mutual-recursive.js",

// .NET regex engine: nullable quantifier and lookahead capture group semantics differ
"built-ins/RegExp/nullable-quantifier.js",
"built-ins/RegExp/lookahead-quantifier-match-groups.js",

// .NET regex engine: complex quantifier capture group tracking differs
"built-ins/RegExp/S15.10.2.5_A1_T4.js",
"built-ins/RegExp/prototype/exec/S15.10.6.2_A1_T6.js",

// .NET regex engine: Unicode case folding tables differ from ECMAScript CaseFolding.txt
"language/literals/regexp/u-case-mapping.js",
"built-ins/RegExp/lookahead-quantifier-match-groups.js",
"built-ins/RegExp/unicode_full_case_folding.js",
"built-ins/RegExp/named-groups/duplicate-names-exec.js",

// .NET regex engine: quantifier values exceeding int.MaxValue
"built-ins/RegExp/quantifier-integer-limit.js",

// .NET regex engine: quantified groups with alternation retain captures from previous iterations
// (JavaScript resets non-participating groups per iteration, .NET does not)
"built-ins/RegExp/named-groups/duplicate-names-exec.js",
"built-ins/RegExp/named-groups/duplicate-names-match.js",
"built-ins/RegExp/named-groups/duplicate-names-match.js",
"built-ins/RegExp/named-groups/duplicate-names-match-indices.js",
"built-ins/RegExp/named-groups/duplicate-names-match-indices.js",
"built-ins/RegExp/named-groups/duplicate-names-replace.js",
"built-ins/RegExp/named-groups/duplicate-names-replace.js",
"built-ins/RegExp/named-groups/duplicate-names-replaceall.js",
"built-ins/RegExp/named-groups/duplicate-names-replaceall.js",
"built-ins/RegExp/prototype/exec/duplicate-named-groups-properties.js",
"built-ins/RegExp/named-groups/duplicate-names-test.js",
"built-ins/RegExp/prototype/exec/duplicate-named-groups-properties.js",
"built-ins/RegExp/prototype/exec/duplicate-named-indices-groups-properties.js",
"built-ins/RegExp/prototype/exec/duplicate-named-indices-groups-properties.js",
"built-ins/String/prototype/match/duplicate-named-groups-properties.js",
"built-ins/String/prototype/match/duplicate-named-groups-properties.js",
"built-ins/String/prototype/match/duplicate-named-indices-groups-properties.js",
"built-ins/String/prototype/match/duplicate-named-indices-groups-properties.js",

// requires investigation how to process complex function name evaluation for property
Expand Down
77 changes: 64 additions & 13 deletions Jint/Native/RegExp/RegExpPrototype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,16 +178,29 @@ string Evaluator(Match match)
replacerArgs.Add(match.Value);

ObjectInstance? groups = null;
// Pre-initialize groups with unique names in order
for (var i = 1; i < actualGroupCount; i++)
{
var groupName = GetRegexGroupName(rei, i);
if (!string.IsNullOrWhiteSpace(groupName))
{
groups ??= OrdinaryObjectCreate(_engine, null);
if (!groups.HasOwnProperty(groupName))
{
groups.CreateDataPropertyOrThrow(groupName, Undefined);
}
}
}

for (var i = 1; i < actualGroupCount; i++)
{
var capture = match.Groups[i];
replacerArgs.Add(capture.Success ? capture.Value : Undefined);

var groupName = GetRegexGroupName(rei, i);
if (!string.IsNullOrWhiteSpace(groupName))
if (!string.IsNullOrWhiteSpace(groupName) && capture.Success)
{
groups ??= OrdinaryObjectCreate(_engine, null);
groups.CreateDataPropertyOrThrow(groupName, capture.Success ? capture.Value : Undefined);
groups!.CreateDataPropertyOrThrow(groupName, (JsString) capture.Value);
}
}

Expand Down Expand Up @@ -953,8 +966,33 @@ private static JsArray CreateReturnValueArray(
array.CreateDataProperty(PropertyInput, s);

ObjectInstance? groups = null;
List<string>? groupNames = null;
List<string?>? groupNames = null;
var indices = hasIndices ? new List<JsNumber[]?>(actualGroupCount) : null;

// Pre-initialize groups object with all unique names in source order.
// This ensures correct property ordering and that duplicate names don't
// overwrite a successful capture with undefined from a non-participating group.
var hasAnyGroupName = false;
for (uint i = 1; i < actualGroupCount; i++)
{
var groupName = GetRegexGroupName(rei, (int) i);
if (!string.IsNullOrWhiteSpace(groupName))
{
hasAnyGroupName = true;
groups ??= OrdinaryObjectCreate(engine, null);
if (!groups.HasOwnProperty(groupName))
{
groups.CreateDataPropertyOrThrow(groupName, Undefined);
}
}

if (hasIndices)
{
groupNames ??= [];
groupNames.Add(groupName);
}
}

for (uint i = 0; i < actualGroupCount; i++)
{
var capture = match.Groups[(int) i];
Expand All @@ -976,13 +1014,13 @@ private static JsArray CreateReturnValueArray(
}
}

var groupName = GetRegexGroupName(rei, (int) i);
if (!string.IsNullOrWhiteSpace(groupName))
if (i > 0)
{
groups ??= OrdinaryObjectCreate(engine, null);
groups.CreateDataPropertyOrThrow(groupName, capturedValue);
groupNames ??= [];
groupNames.Add(groupName!);
var groupName = GetRegexGroupName(rei, (int) i);
if (!string.IsNullOrWhiteSpace(groupName) && capture?.Success == true)
{
groups!.CreateDataPropertyOrThrow(groupName, capturedValue);
}
}

array.SetIndexValue(i, capturedValue, updateLength: false);
Expand All @@ -992,7 +1030,7 @@ private static JsArray CreateReturnValueArray(

if (hasIndices)
{
var indicesArray = MakeMatchIndicesIndexPairArray(engine, s, indices!, groupNames, groupNames?.Count > 0);
var indicesArray = MakeMatchIndicesIndexPairArray(engine, s, indices!, groupNames, hasAnyGroupName);
array.CreateDataPropertyOrThrow("indices", indicesArray);
}

Expand Down Expand Up @@ -1038,7 +1076,7 @@ private static JsArray MakeMatchIndicesIndexPairArray(
Engine engine,
string s,
List<JsNumber[]?> indices,
List<string>? groupNames,
List<string?>? groupNames,
bool hasGroups)
{
var n = indices.Count;
Expand All @@ -1047,6 +1085,15 @@ private static JsArray MakeMatchIndicesIndexPairArray(
if (hasGroups)
{
groups = OrdinaryObjectCreate(engine, null);

// Pre-initialize all unique group names with undefined for correct property ordering
foreach (var name in groupNames!)
{
if (!string.IsNullOrWhiteSpace(name) && !groups.HasOwnProperty(name))
{
groups.CreateDataPropertyOrThrow(name, Undefined);
}
}
}

a.CreateDataPropertyOrThrow("groups", groups ?? Undefined);
Expand All @@ -1061,7 +1108,11 @@ private static JsArray MakeMatchIndicesIndexPairArray(
a.Push(matchIndexPair);
if (i > 0 && !string.IsNullOrWhiteSpace(groupNames?[i - 1]))
{
groups!.CreateDataPropertyOrThrow(groupNames![i - 1], matchIndexPair);
// For duplicate group names, only update if this group actually matched
if (matchIndices is not null)
{
groups!.CreateDataPropertyOrThrow(groupNames![i - 1], matchIndexPair);
}
}
}
return a;
Expand Down