diff --git a/features.txt b/features.txt
index bd50c2957f2..a3304a46585 100644
--- a/features.txt
+++ b/features.txt
@@ -141,6 +141,10 @@ optional-chaining
# https://github.com/tc39/proposal-top-level-await
top-level-await
+# RegExp Match Array Indices
+# https://github.com/tc39/proposal-regexp-match-indices
+regexp-match-indices
+
## Standard language features
#
# Language features that have been included in a published version of the
diff --git a/harness/assert.js b/harness/assert.js
index cd548d895af..c777a4c2f63 100644
--- a/harness/assert.js
+++ b/harness/assert.js
@@ -5,6 +5,9 @@ description: |
Collection of assertion functions used throughout test262
---*/
+///
+///
+
function assert(mustBeTrue, message) {
if (mustBeTrue === true) {
return;
@@ -92,6 +95,58 @@ assert.throws = function (expectedErrorConstructor, func, message) {
$ERROR(message);
};
+assert._formatValue = function(value, seen) {
+ switch (typeof value) {
+ case 'string':
+ return typeof JSON !== "undefined" ? JSON.stringify(value) : '"' + value + '"';
+ case 'number':
+ case 'boolean':
+ case 'symbol':
+ case 'bigint':
+ return value.toString();
+ case 'undefined':
+ return 'undefined';
+ case 'function':
+ return '[Function' + (value.name ? ': ' + value.name : '') + ']';
+ case 'object':
+ if (value === null) return 'null';
+ if (value instanceof Date) return 'Date "' + value.toISOString() + '"';
+ if (value instanceof RegExp) return value.toString();
+ if (!seen) {
+ seen = {
+ counter: 0,
+ map: new Map()
+ };
+ }
+
+ var usage = seen.map.get(value);
+ if (usage) {
+ usage.used = true;
+ return '[Ref: #' + usage.id + ']';
+ }
+
+ usage = { id: ++seen.counter, used: false };
+ seen.map.set(value, usage);
+
+ if (typeof Set !== "undefined" && value instanceof Set) {
+ return 'Set {' + Array.from(value).map(function (value) { return assert._formatValue(value, seen); }).join(', ') + '}' + (usage.used ? ' as #' + usage.id : '');
+ }
+ if (typeof Map !== "undefined" && value instanceof Map) {
+ return 'Map {' + Array.from(value).map(function (pair) { return assert._formatValue(pair[0], seen) + ' => ' + assert._formatValue(pair[1], seen) + '}'; }).join(', ') + '}' + (usage.used ? ' as #' + usage.id : '');
+ }
+ if (Array.isArray ? Array.isArray(value) : value instanceof Array) {
+ return '[' + value.map(function (value) { return assert._formatValue(value, seen); }).join(', ') + ']' + (usage.used ? ' as #' + usage.id : '');
+ }
+ var tag = Symbol.toStringTag in value ? value[Symbol.toStringTag] : 'Object';
+ if (tag === 'Object' && Object.getPrototypeOf(value) === null) {
+ tag = '[Object: null prototype]';
+ }
+ return (tag ? tag + ' ' : '') + '{ ' + Object.keys(value).map(function (key) { return key.toString() + ': ' + assert._formatValue(value[key], seen); }).join(', ') + ' }' + (usage.used ? ' as #' + usage.id : '');
+ default:
+ return typeof value;
+ }
+};
+
assert._toString = function (value) {
try {
return String(value);
diff --git a/harness/compareArray.js b/harness/compareArray.js
index 38a5d9a5a69..a95c7533d2b 100644
--- a/harness/compareArray.js
+++ b/harness/compareArray.js
@@ -5,6 +5,9 @@ description: |
Compare the contents of two arrays
---*/
+// @ts-check
+///
+
function isSameValue(a, b) {
if (a === 0 && b === 0) return 1 / a === 1 / b;
if (a !== a && b !== b) return true;
@@ -12,6 +15,11 @@ function isSameValue(a, b) {
return a === b;
}
+/**
+ * @template T
+ * @param {T[]} a
+ * @param {T[]} b
+ */
function compareArray(a, b) {
if (b.length !== a.length) {
return false;
@@ -25,11 +33,13 @@ function compareArray(a, b) {
return true;
}
+/**
+ * @template T
+ * @param {T[]} actual
+ * @param {T[]} expected
+ * @param {string} [message]
+ */
assert.compareArray = function(actual, expected, message) {
- function formatArray(array) {
- return '[' + array.map(String).join(', ') + ']';
- }
-
assert(compareArray(actual, expected),
- 'Expected ' + formatArray(actual) + ' and ' + formatArray(expected) + ' to have the same contents. ' + message);
+ 'Expected ' + assert._formatValue(actual) + ' and ' + assert._formatValue(expected) + ' to have the same contents. ' + message);
};
diff --git a/harness/deepEqual.js b/harness/deepEqual.js
new file mode 100644
index 00000000000..5266035fd37
--- /dev/null
+++ b/harness/deepEqual.js
@@ -0,0 +1,415 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+description: |
+ Compare two values structurally
+---*/
+
+// @ts-check
+///
+
+var deepEqual = (function () {
+ /**
+ * @typedef {0} UNKNOWN
+ * @typedef {1} EQUAL
+ * @typedef {-1} NOT_EQUAL
+ * @typedef {Map>} ComparisonCache
+ */
+
+ /** @type {EQUAL} */
+ var EQUAL = 1;
+
+ /** @type {NOT_EQUAL} */
+ var NOT_EQUAL = -1;
+
+ /** @type {UNKNOWN} */
+ var UNKNOWN = 0;
+
+ /**
+ * @template T
+ * @param {T} a
+ * @param {T} b
+ * @returns {boolean}
+ */
+ function deepEqual(a, b) {
+ return compareEquality(a, b) === EQUAL;
+ }
+
+ /**
+ * @param {unknown} a
+ * @param {unknown} b
+ * @param {ComparisonCache} [cache]
+ * @returns {EQUAL | NOT_EQUAL}
+ */
+ function compareEquality(a, b, cache) {
+ return compareIf(a, b, isOptional, compareOptionality)
+ || compareIf(a, b, isPrimitiveEquatable, comparePrimitiveEquality)
+ || compareIf(a, b, isObjectEquatable, compareObjectEquality, cache)
+ || NOT_EQUAL;
+ }
+
+ /**
+ * @template T
+ * @param {unknown} a
+ * @param {unknown} b
+ * @param {(value: unknown) => value is T} test
+ * @param {(a: T, b: T, cache?: ComparisonCache) => EQUAL | NOT_EQUAL} compare
+ * @param {ComparisonCache} [cache]
+ * @returns {EQUAL | NOT_EQUAL | UNKNOWN}
+ */
+ function compareIf(a, b, test, compare, cache) {
+ return !test(a)
+ ? !test(b) ? UNKNOWN : NOT_EQUAL
+ : !test(b) ? NOT_EQUAL : cacheComparison(a, b, compare, cache);
+ }
+
+ /**
+ * @returns {EQUAL | UNKNOWN}
+ */
+ function tryCompareStrictEquality(a, b) {
+ return a === b ? EQUAL : UNKNOWN;
+ }
+
+ /**
+ * @returns {NOT_EQUAL | UNKNOWN}
+ */
+ function tryCompareTypeOfEquality(a, b) {
+ return typeof a !== typeof b ? NOT_EQUAL : UNKNOWN;
+ }
+
+ /**
+ * @returns {NOT_EQUAL | UNKNOWN}
+ */
+ function tryCompareToStringTagEquality(a, b) {
+ var aTag = Symbol.toStringTag in a ? a[Symbol.toStringTag] : undefined;
+ var bTag = Symbol.toStringTag in b ? b[Symbol.toStringTag] : undefined;
+ return aTag !== bTag ? NOT_EQUAL : UNKNOWN;
+ }
+
+ /**
+ * @returns {value is null | undefined}
+ */
+ function isOptional(value) {
+ return value === undefined
+ || value === null;
+ }
+
+ /**
+ * @returns {EQUAL | NOT_EQUAL}
+ */
+ function compareOptionality(a, b) {
+ return tryCompareStrictEquality(a, b)
+ || NOT_EQUAL;
+ }
+
+ /**
+ * @returns {value is number | bigint | string | symbol | boolean | undefined}
+ */
+ function isPrimitiveEquatable(value) {
+ switch (typeof value) {
+ case 'string':
+ case 'number':
+ case 'bigint':
+ case 'boolean':
+ case 'symbol':
+ return true;
+ default:
+ return isBoxed(value);
+ }
+ }
+
+ /**
+ * @returns {EQUAL | NOT_EQUAL}
+ */
+ function comparePrimitiveEquality(a, b) {
+ if (isBoxed(a)) a = a.valueOf();
+ if (isBoxed(b)) b = b.valueOf();
+ return tryCompareStrictEquality(a, b)
+ || tryCompareTypeOfEquality(a, b)
+ || compareIf(a, b, isNaNEquatable, compareNaNEquality)
+ || NOT_EQUAL;
+ }
+
+ /**
+ * @returns {value is number}
+ */
+ function isNaNEquatable(value) {
+ return typeof value === 'number';
+ }
+
+ /**
+ * @param {number} a
+ * @param {number} b
+ * @returns {EQUAL | NOT_EQUAL}
+ */
+ function compareNaNEquality(a, b) {
+ return isNaN(a) && isNaN(b) ? EQUAL : NOT_EQUAL;
+ }
+
+ /**
+ * @returns {value is object}
+ */
+ function isObjectEquatable(value) {
+ return typeof value === 'object';
+ }
+
+ /**
+ * @param {ComparisonCache} cache
+ * @returns {EQUAL | NOT_EQUAL}
+ */
+ function compareObjectEquality(a, b, cache) {
+ if (!cache) cache = new Map();
+ return getCache(cache, a, b)
+ || setCache(cache, a, b, EQUAL) // consider equal for now
+ || cacheComparison(a, b, tryCompareStrictEquality, cache)
+ || cacheComparison(a, b, tryCompareToStringTagEquality, cache)
+ || compareIf(a, b, isValueOfEquatable, compareValueOfEquality)
+ || compareIf(a, b, isToStringEquatable, compareToStringEquality)
+ || compareIf(a, b, isArrayLikeEquatable, compareArrayLikeEquality, cache)
+ || compareIf(a, b, isStructurallyEquatable, compareStructuralEquality, cache)
+ || compareIf(a, b, isIterableEquatable, compareIterableEquality, cache)
+ || cacheComparison(a, b, fail, cache);
+ }
+
+ function isBoxed(value) {
+ return value instanceof String
+ || value instanceof Number
+ || value instanceof Boolean
+ || typeof Symbol === 'function' && value instanceof Symbol
+ || typeof BigInt === 'function' && value instanceof BigInt;
+ }
+
+ /**
+ * @returns {value is { valueOf(): any }}
+ */
+ function isValueOfEquatable(value) {
+ return value instanceof Date;
+ }
+
+ /**
+ * @param {{ valueOf(): any }} a
+ * @param {{ valueOf(): any }} b
+ * @returns {EQUAL | NOT_EQUAL}
+ */
+ function compareValueOfEquality(a, b) {
+ return compareIf(a.valueOf(), b.valueOf(), isPrimitiveEquatable, comparePrimitiveEquality)
+ || NOT_EQUAL;
+ }
+
+ /**
+ * @returns {value is { toString(): string }}
+ */
+ function isToStringEquatable(value) {
+ return value instanceof RegExp;
+ }
+
+ /**
+ * @param {{ toString(): string }} a
+ * @param {{ toString(): string }} b
+ * @returns {EQUAL | NOT_EQUAL}
+ */
+ function compareToStringEquality(a, b) {
+ return compareIf(a.toString(), b.toString(), isPrimitiveEquatable, comparePrimitiveEquality)
+ || NOT_EQUAL;
+ }
+
+ /**
+ * @returns {value is ArrayLike}
+ */
+ function isArrayLikeEquatable(value) {
+ return (Array.isArray ? Array.isArray(value) : value instanceof Array)
+ || (typeof Uint8Array === 'function' && value instanceof Uint8Array)
+ || (typeof Uint8ClampedArray === 'function' && value instanceof Uint8ClampedArray)
+ || (typeof Uint16Array === 'function' && value instanceof Uint16Array)
+ || (typeof Uint32Array === 'function' && value instanceof Uint32Array)
+ || (typeof Int8Array === 'function' && value instanceof Int8Array)
+ || (typeof Int16Array === 'function' && value instanceof Int16Array)
+ || (typeof Int32Array === 'function' && value instanceof Int32Array)
+ || (typeof Float32Array === 'function' && value instanceof Float32Array)
+ || (typeof Float64Array === 'function' && value instanceof Float64Array)
+ || (typeof BigUint64Array === 'function' && value instanceof BigUint64Array)
+ || (typeof BigInt64Array === 'function' && value instanceof BigInt64Array);
+ }
+
+ /**
+ * @template T
+ * @param {ArrayLike} a
+ * @param {ArrayLike} b
+ * @param {ComparisonCache} cache
+ * @returns {EQUAL | NOT_EQUAL}
+ */
+ function compareArrayLikeEquality(a, b, cache) {
+ if (a.length !== b.length) return NOT_EQUAL;
+ for (var i = 0; i < a.length; i++) {
+ if (compareEquality(a[i], b[i], cache) === NOT_EQUAL) {
+ return NOT_EQUAL;
+ }
+ }
+ return EQUAL;
+ }
+
+ /**
+ * @template T
+ * @param {T} value
+ * @returns {value is Exclude}
+ */
+ function isStructurallyEquatable(value) {
+ return !(typeof Promise === 'function' && value instanceof Promise // only comparable by reference
+ || typeof WeakMap === 'function' && value instanceof WeakMap // only comparable by reference
+ || typeof WeakSet === 'function' && value instanceof WeakSet // only comparable by reference
+ || typeof Map === 'function' && value instanceof Map // comparable via @@iterator
+ || typeof Set === 'function' && value instanceof Set); // comparable via @@iterator
+ }
+
+ /**
+ * @param {ComparisonCache} cache
+ * @returns {EQUAL | NOT_EQUAL}
+ */
+ function compareStructuralEquality(a, b, cache) {
+ var aKeys = [];
+ for (var key in a) aKeys.push(key);
+
+ var bKeys = [];
+ for (var key in b) bKeys.push(key);
+
+ if (aKeys.length !== bKeys.length) {
+ return NOT_EQUAL;
+ }
+
+ aKeys.sort();
+ bKeys.sort();
+
+ for (var i = 0; i < aKeys.length; i++) {
+ var aKey = aKeys[i];
+ var bKey = bKeys[i];
+ if (compareEquality(aKey, bKey, cache) === NOT_EQUAL) {
+ return NOT_EQUAL;
+ }
+ if (compareEquality(a[aKey], b[bKey], cache) === NOT_EQUAL) {
+ return NOT_EQUAL;
+ }
+ }
+
+ return compareIf(a, b, isIterableEquatable, compareIterableEquality, cache)
+ || EQUAL;
+ }
+
+ /**
+ * @returns {value is Iterable}
+ */
+ function isIterableEquatable(value) {
+ return typeof Symbol === 'function'
+ && typeof value[Symbol.iterator] === 'function';
+ }
+
+ /**
+ * @template T
+ * @param {Iterator} a
+ * @param {Iterator} b
+ * @param {ComparisonCache} cache
+ * @returns {EQUAL | NOT_EQUAL}
+ */
+ function compareIteratorEquality(a, b, cache) {
+ if (typeof Map === 'function' && a instanceof Map && b instanceof Map ||
+ typeof Set === 'function' && a instanceof Set && b instanceof Set) {
+ if (a.size !== b.size) return NOT_EQUAL; // exit early if we detect a difference in size
+ }
+
+ var ar, br;
+ while (true) {
+ ar = a.next();
+ br = b.next();
+ if (ar.done) {
+ if (br.done) return EQUAL;
+ if (b.return) b.return();
+ return NOT_EQUAL;
+ }
+ if (br.done) {
+ if (a.return) a.return();
+ return NOT_EQUAL;
+ }
+ if (compareEquality(ar.value, br.value, cache) === NOT_EQUAL) {
+ if (a.return) a.return();
+ if (b.return) b.return();
+ return NOT_EQUAL;
+ }
+ }
+ }
+
+ /**
+ * @template T
+ * @param {Iterable} a
+ * @param {Iterable} b
+ * @param {ComparisonCache} cache
+ * @returns {EQUAL | NOT_EQUAL}
+ */
+ function compareIterableEquality(a, b, cache) {
+ return compareIteratorEquality(a[Symbol.iterator](), b[Symbol.iterator](), cache);
+ }
+
+ /**
+ * @template T
+ * @template {EQUAL | NOT_EQUAL | UNKNOWN} R
+ * @param {(a: T, b: T, circular?: ComparisonCache) => R} compare
+ * @param {ComparisonCache} [cache]
+ */
+ function cacheComparison(a, b, compare, cache) {
+ var result = compare(a, b, cache);
+ if (cache && (result === EQUAL || result === NOT_EQUAL)) {
+ setCache(cache, a, b, /** @type {EQUAL | NOT_EQUAL} */(result));
+ }
+ return result;
+ }
+
+ function fail() {
+ return NOT_EQUAL;
+ }
+
+ /**
+ * @param {EQUAL | NOT_EQUAL} result
+ * @param {ComparisonCache} cache
+ */
+ function setCache(cache, left, right, result) {
+ var otherCache;
+
+ otherCache = cache.get(left);
+ if (!otherCache) cache.set(left, otherCache = new Map());
+ otherCache.set(right, result);
+
+ otherCache = cache.get(right);
+ if (!otherCache) cache.set(right, otherCache = new Map());
+ otherCache.set(left, result);
+ }
+
+ /**
+ * @param {ComparisonCache} cache
+ */
+ function getCache(cache, left, right) {
+ var otherCache;
+ /** @type {EQUAL | NOT_EQUAL | UNKNOWN | undefined} */
+ var result;
+
+ otherCache = cache.get(left);
+ result = otherCache && otherCache.get(right);
+ if (result) return result;
+
+ otherCache = cache.get(right);
+ result = otherCache && otherCache.get(left);
+ if (result) return result;
+
+ return UNKNOWN;
+ }
+
+ return deepEqual;
+})();
+
+/**
+ * @template T
+ * @param {T} actual
+ * @param {T} expected
+ * @param {string} [message]
+ */
+assert.deepEqual = function (actual, expected, message) {
+ assert(deepEqual(actual, expected),
+ 'Expected ' + assert._formatValue(actual) + ' to be structurally equal to ' + assert._formatValue(expected) + '. ' + (message || ''));
+};
diff --git a/harness/propertyHelper.js b/harness/propertyHelper.js
index a3a57a1bebe..f4c3158eb1a 100644
--- a/harness/propertyHelper.js
+++ b/harness/propertyHelper.js
@@ -6,6 +6,16 @@ description: |
property descriptors.
---*/
+// @ts-check
+///
+
+/**
+ * @param {object} obj
+ * @param {string|symbol} name
+ * @param {PropertyDescriptor|undefined} desc
+ * @param {object} [options]
+ * @param {boolean} [options.restore]
+ */
function verifyProperty(obj, name, desc, options) {
assert(
arguments.length > 2,
diff --git a/harness/types.d.ts b/harness/types.d.ts
new file mode 100644
index 00000000000..b3d415ad60e
--- /dev/null
+++ b/harness/types.d.ts
@@ -0,0 +1,14 @@
+declare function $ERROR(text: string): void;
+
+// Proposal: regexp-match-indices
+interface RegExpExecArray {
+ indices: RegExpIndicesArray;
+}
+
+interface RegExpMatchArray {
+ indices: RegExpIndicesArray;
+}
+
+interface RegExpIndicesArray extends Array<[number, number]> {
+ groups?: { [group: string]: [number, number] };
+}
diff --git a/test/built-ins/RegExp/match-indices/indices-array-element.js b/test/built-ins/RegExp/match-indices/indices-array-element.js
new file mode 100644
index 00000000000..aebfd5772a7
--- /dev/null
+++ b/test/built-ins/RegExp/match-indices/indices-array-element.js
@@ -0,0 +1,31 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: A matching element of indices is an Array with exactly two number properties.
+includes: [compareArray.js]
+esid: sec-getmatchindicesarray
+features: [regexp-match-indices]
+info: |
+ GetMatchIndicesArray ( S, match )
+ 5. Return CreateArrayFromList(ยซ _match_.[[StartIndex]], _match_.[[EndIndex]] ยป).
+---*/
+
+///
+///
+
+let input = "abcd";
+let match = /b(c)/.exec(input);
+let indices = match.indices;
+
+// `indices[0]` is an array
+assert.sameValue(Object.getPrototypeOf(indices[0]), Array.prototype);
+assert.sameValue(indices[0].length, 2);
+assert.sameValue(typeof indices[0][0], "number");
+assert.sameValue(typeof indices[0][1], "number");
+
+// `indices[1]` is an array
+assert.sameValue(Object.getPrototypeOf(indices[1]), Array.prototype);
+assert.sameValue(indices[1].length, 2);
+assert.sameValue(typeof indices[1][0], "number");
+assert.sameValue(typeof indices[1][1], "number");
diff --git a/test/built-ins/RegExp/match-indices/indices-array-matched.js b/test/built-ins/RegExp/match-indices/indices-array-matched.js
new file mode 100644
index 00000000000..c0b42478e49
--- /dev/null
+++ b/test/built-ins/RegExp/match-indices/indices-array-matched.js
@@ -0,0 +1,41 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: The properties of the "indices" array correspond to the start/end indices of the same values in the match.
+includes: [compareArray.js]
+esid: sec-makeindicesarray
+features: [regexp-match-indices]
+info: |
+ MakeIndicesArray ( S, indices, groupNames )
+ 4. Let _n_ be the number of elements in _indices_.
+ ...
+ 6. Set _A_ to ! ArrayCreate(_n_).
+ ...
+ 11. For each integer _i_ such that _i_ >= 0 and _i_ < _n_, do
+ a. Let _matchIndices_ be _indices_[_i_].
+ b. If _matchIndices_ is not *undefined*, then
+ i. Let _matchIndicesArray_ be ! GetMatchIndicesArray(_S_, _matchIndices_).
+ c. Else,
+ i. Let _matchIndicesArray_ be *undefined*.
+ d. Perform ! CreateDataProperty(_A_, ! ToString(_n_), _matchIndicesArray_).
+ ...
+---*/
+
+///
+///
+
+let input = "abcd";
+let match = /b(c)/.exec(input);
+let indices = match.indices;
+
+// `indices` has the same length as match
+assert.sameValue(indices.length, match.length);
+
+// The first element of `indices` contains the start/end indices of the match
+assert.compareArray(indices[0], [1, 3]);
+assert.sameValue(input.slice(indices[0][0], indices[0][1]), match[0]);
+
+// The second element of `indices` contains the start/end indices of the first capture
+assert.compareArray(indices[1], [2, 3]);
+assert.sameValue(input.slice(indices[1][0], indices[1][1]), match[1]);
diff --git a/test/built-ins/RegExp/match-indices/indices-array-non-unicode-match.js b/test/built-ins/RegExp/match-indices/indices-array-non-unicode-match.js
new file mode 100644
index 00000000000..7e0ff3481d4
--- /dev/null
+++ b/test/built-ins/RegExp/match-indices/indices-array-non-unicode-match.js
@@ -0,0 +1,80 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Basic matching cases with non-unicode matches.
+includes: [compareArray.js, propertyHelper.js, deepEqual.js]
+esid: sec-regexpbuiltinexec
+features: [regexp-match-indices]
+info: |
+ Runtime Semantics: RegExpBuiltinExec ( R, S )
+ ...
+ 4. Let _lastIndex_ be ? ToLength(? Get(_R_, `"lastIndex")).
+ ...
+ 25. Let _indices_ be a new empty List.
+ 26. Let _match_ be the Match { [[StartIndex]]: _lastIndex_, [[EndIndex]]: _e_ }.
+ 27. Add _match_ as the last element of _indices_.
+ ...
+ 33. For each integer _i_ such that _i_ > 0 and _i_ <= _n_, in ascending order, do
+ ...
+ f. Else,
+ i. Let _captureStart_ be _captureI_'s _startIndex_.
+ ii. Let _captureEnd_ be _captureI_'s _endIndex_.
+ ...
+ iv. Let _capture_ be the Match { [[StartIndex]]: _captureStart_, [[EndIndex]]: _captureEnd_ }.
+ v. Append _capture_ to _indices_.
+ ...
+ 34. Let _indicesArray_ be MakeIndicesArray( _S_, _indices_, _groupNames_).
+---*/
+
+///
+///
+///
+///
+
+assert.deepEqual([[1, 2], [1, 2]], "bab".match(/(a)/).indices);
+assert.deepEqual([[0, 3], [1, 2]], "bab".match(/.(a)./).indices);
+assert.deepEqual([[0, 3], [1, 2], [2, 3]], "bab".match(/.(a)(.)/).indices);
+assert.deepEqual([[0, 3], [1, 3]], "bab".match(/.(\w\w)/).indices);
+assert.deepEqual([[0, 3], [0, 3]], "bab".match(/(\w\w\w)/).indices);
+assert.deepEqual([[0, 3], [0, 2], [2, 3]], "bab".match(/(\w\w)(\w)/).indices);
+assert.deepEqual([[0, 2], [0, 2], undefined], "bab".match(/(\w\w)(\W)?/).indices);
+
+let groups = /(?.)(?.)(?.)\k\k\k/.exec("abccba").indices.groups;
+assert.compareArray([0, 1], groups.a);
+assert.compareArray([1, 2], groups.b);
+assert.compareArray([2, 3], groups.c);
+verifyProperty(groups, "a", {
+ enumerable: true,
+ writable: true,
+ configurable: true
+});
+verifyProperty(groups, "b", {
+ enumerable: true,
+ writable: true,
+ configurable: true
+});
+verifyProperty(groups, "c", {
+ enumerable: true,
+ writable: true,
+ configurable: true
+});
+
+// "๐" is U+1d401 MATHEMATICAL BOLD CAPITAL B
+// - Also representable as the code point "\u{1d401}"
+// - Also representable as the surrogate pair "\uD835\uDC01"
+
+// Verify assumptions:
+assert.sameValue("๐".length, 2, 'The length of "๐" is 2');
+assert.sameValue("\u{1d401}".length, 2, 'The length of "\\u{1d401}" is 2');
+assert.sameValue("\uD835\uDC01".length, 2, 'The length of "\\uD835\\uDC01" is 2');
+assert.sameValue("๐".match(/./)[0].length, 1, 'The length of a single code unit match against "๐" is 1 (without /u flag)');
+assert.sameValue("\u{1d401}".match(/./)[0].length, 1, 'The length of a single code unit match against "\\u{1d401}" is 1 (without /u flag)');
+assert.sameValue("\uD835\uDC01".match(/./)[0].length, 1, 'The length of a single code unit match against "\\ud835\\udc01" is 1 (without /u flag)');
+
+assert.compareArray([0, 1], "๐".match(/./).indices[0], 'Indices for non-unicode match against "๐" (without /u flag)');
+assert.compareArray([0, 1], "\u{1d401}".match(/./).indices[0], 'Indices for non-unicode match against "\\u{1d401}" (without /u flag)');
+assert.compareArray([0, 1], "\uD835\uDC01".match(/./).indices[0], 'Indices for non-unicode match against "\\ud835\\udc01" (without /u flag)');
+assert.compareArray([0, 1], "๐".match(/(?.)/).indices.groups.a, 'Indices for non-unicode match against "๐" in groups.a (without /u flag)');
+assert.compareArray([0, 1], "\u{1d401}".match(/(?.)/).indices.groups.a, 'Indices for non-unicode match against "\\u{1d401}" in groups.a (without /u flag)');
+assert.compareArray([0, 1], "\uD835\uDC01".match(/(?.)/).indices.groups.a, 'Indices for non-unicode match against "\\ud835\\udc01" in groups.a (without /u flag)');
diff --git a/test/built-ins/RegExp/match-indices/indices-array-properties.js b/test/built-ins/RegExp/match-indices/indices-array-properties.js
new file mode 100644
index 00000000000..6f48381ae66
--- /dev/null
+++ b/test/built-ins/RegExp/match-indices/indices-array-properties.js
@@ -0,0 +1,32 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: The properties of the "indices" array are created with CreateDataProperty.
+includes: [propertyHelper.js]
+esid: sec-makeindicesarray
+features: [regexp-match-indices]
+info: |
+ MakeIndicesArray ( S, indices, groupNames )
+ 11. For each integer _i_ such that _i_ >= 0 and _i_ < _n_, do
+ d. Perform ! CreateDataProperty(_A_, ! ToString(_n_), _matchIndicesArray_).
+---*/
+
+///
+///
+
+let input = "abcd";
+let match = /b(c)/.exec(input);
+let indices = match.indices;
+
+verifyProperty(indices, '0', {
+ enumerable: true,
+ configurable: true,
+ writable: true
+});
+
+verifyProperty(indices, '1', {
+ enumerable: true,
+ configurable: true,
+ writable: true
+});
diff --git a/test/built-ins/RegExp/match-indices/indices-array-unicode-match.js b/test/built-ins/RegExp/match-indices/indices-array-unicode-match.js
new file mode 100644
index 00000000000..b4b8018157d
--- /dev/null
+++ b/test/built-ins/RegExp/match-indices/indices-array-unicode-match.js
@@ -0,0 +1,89 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Basic matching cases with non-unicode matches.
+includes: [compareArray.js, propertyHelper.js, deepEqual.js]
+esid: sec-regexpbuiltinexec
+features: [regexp-match-indices]
+info: |
+ Runtime Semantics: RegExpBuiltinExec ( R, S )
+ ...
+ 4. Let _lastIndex_ be ? ToLength(? Get(_R_, `"lastIndex")).
+ ...
+ 16. If _fullUnicode_ is *true*, set _e_ to ! GetStringIndex(_S_, _Input_, _e_).
+ ...
+ 25. Let _indices_ be a new empty List.
+ 26. Let _match_ be the Match { [[StartIndex]]: _lastIndex_, [[EndIndex]]: _e_ }.
+ 27. Add _match_ as the last element of _indices_.
+ ...
+ 33. For each integer _i_ such that _i_ > 0 and _i_ <= _n_, in ascending order, do
+ ...
+ f. Else,
+ i. Let _captureStart_ be _captureI_'s _startIndex_.
+ ii. Let _captureEnd_ be _captureI_'s _endIndex_.
+ iii. If _fullUnicode_ is *true*, then
+ 1. Set _captureStart_ to ! GetStringIndex(_S_, _Input_, _captureStart_).
+ 1. Set _captureEnd_ to ! GetStringIndex(_S_, _Input_, _captureEnd_).
+ iv. Let _capture_ be the Match { [[StartIndex]]: _captureStart_, [[EndIndex]]: _captureEnd_ }.
+ v. Append _capture_ to _indices_.
+ ...
+ 34. Let _indicesArray_ be MakeIndicesArray( _S_, _indices_, _groupNames_).
+
+ GetStringIndex ( S, Input, e )
+ ...
+ 4. Let _eUTF_ be the smallest index into _S_ that corresponds to the character at element _e_ of _Input_. If _e_ is greater than or equal to the number of elements in _Input_, then _eUTF_ is the number of code units in _S_.
+ 5. Return _eUTF_.
+---*/
+
+///
+///
+///
+///
+
+assert.deepEqual([[1, 2], [1, 2]], "bab".match(/(a)/u).indices);
+assert.deepEqual([[0, 3], [1, 2]], "bab".match(/.(a)./u).indices);
+assert.deepEqual([[0, 3], [1, 2], [2, 3]], "bab".match(/.(a)(.)/u).indices);
+assert.deepEqual([[0, 3], [1, 3]], "bab".match(/.(\w\w)/u).indices);
+assert.deepEqual([[0, 3], [0, 3]], "bab".match(/(\w\w\w)/u).indices);
+assert.deepEqual([[0, 3], [0, 2], [2, 3]], "bab".match(/(\w\w)(\w)/u).indices);
+assert.deepEqual([[0, 2], [0, 2], undefined], "bab".match(/(\w\w)(\W)?/u).indices);
+
+let groups = /(?.)(?.)(?.)\k\k\k/u.exec("abccba").indices.groups;
+assert.compareArray([0, 1], groups.a);
+assert.compareArray([1, 2], groups.b);
+assert.compareArray([2, 3], groups.c);
+verifyProperty(groups, "a", {
+ enumerable: true,
+ writable: true,
+ configurable: true
+});
+verifyProperty(groups, "b", {
+ enumerable: true,
+ writable: true,
+ configurable: true
+});
+verifyProperty(groups, "c", {
+ enumerable: true,
+ writable: true,
+ configurable: true
+});
+
+// "๐" is U+1d401 MATHEMATICAL BOLD CAPITAL B
+// - Also representable as the code point "\u{1d401}"
+// - Also representable as the surrogate pair "\uD835\uDC01"
+
+// Verify assumptions:
+assert.sameValue("๐".length, 2, 'The length of "๐" is 2');
+assert.sameValue("\u{1d401}".length, 2, 'The length of "\\u{1d401}" is 2');
+assert.sameValue("\uD835\uDC01".length, 2, 'The length of "\\uD835\\uDC01" is 2');
+assert.sameValue(2, "๐".match(/./u)[0].length, 'The length of a single code point match against "๐" is 2 (with /u flag)');
+assert.sameValue(2, "\u{1d401}".match(/./u)[0].length, 'The length of a single code point match against "\\u{1d401}" is 2 (with /u flag)');
+assert.sameValue(2, "\uD835\uDC01".match(/./u)[0].length, 'The length of a single code point match against "\\ud835\\udc01" is 2 (with /u flag)');
+
+assert.compareArray([0, 2], "๐".match(/./u).indices[0], 'Indices for unicode match against "๐" (with /u flag)');
+assert.compareArray([0, 2], "\u{1d401}".match(/./u).indices[0], 'Indices for unicode match against \\u{1d401} (with /u flag)');
+assert.compareArray([0, 2], "\uD835\uDC01".match(/./u).indices[0], 'Indices for unicode match against \\ud835\\udc01 (with /u flag)');
+assert.compareArray([0, 2], "๐".match(/(?.)/u).indices.groups.a, 'Indices for unicode match against ๐ in groups.a (with /u flag)');
+assert.compareArray([0, 2], "\u{1d401}".match(/(?.)/u).indices.groups.a, 'Indices for unicode match against \\u{1d401} in groups.a (with /u flag)');
+assert.compareArray([0, 2], "\uD835\uDC01".match(/(?.)/u).indices.groups.a, 'Indices for unicode match against \\ud835\\udc01 in groups.a (with /u flag)');
diff --git a/test/built-ins/RegExp/match-indices/indices-array-unicode-property-names.js b/test/built-ins/RegExp/match-indices/indices-array-unicode-property-names.js
new file mode 100644
index 00000000000..875ce165d64
--- /dev/null
+++ b/test/built-ins/RegExp/match-indices/indices-array-unicode-property-names.js
@@ -0,0 +1,23 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Basic matching cases with non-unicode matches.
+includes: [compareArray.js]
+esid: sec-makeindicesarray
+features: [regexp-match-indices]
+---*/
+
+///
+///
+
+assert.compareArray([1, 2], /(?<ฯ>a)/u.exec("bab").indices.groups.ฯ);
+assert.compareArray([1, 2], /(?<\u{03C0}>a)/u.exec("bab").indices.groups.ฯ);
+assert.compareArray([1, 2], /(?<ฯ>a)/u.exec("bab").indices.groups.\u03C0);
+assert.compareArray([1, 2], /(?<\u{03C0}>a)/u.exec("bab").indices.groups.\u03C0);
+assert.compareArray([1, 2], /(?<$>a)/u.exec("bab").indices.groups.$);
+assert.compareArray([1, 2], /(?<_>a)/u.exec("bab").indices.groups._);
+assert.compareArray([1, 2], /(?<$๐ค>a)/u.exec("bab").indices.groups.$๐ค);
+assert.compareArray([1, 2], /(?<_\u200C>a)/u.exec("bab").indices.groups._\u200C);
+assert.compareArray([1, 2], /(?<_\u200D>a)/u.exec("bab").indices.groups._\u200D);
+assert.compareArray([1, 2], /(?<เฒ _เฒ >a)/u.exec("bab").indices.groups.เฒ _เฒ );
diff --git a/test/built-ins/RegExp/match-indices/indices-array-unmatched.js b/test/built-ins/RegExp/match-indices/indices-array-unmatched.js
new file mode 100644
index 00000000000..17c228eab9a
--- /dev/null
+++ b/test/built-ins/RegExp/match-indices/indices-array-unmatched.js
@@ -0,0 +1,34 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: An unmatched capture in a match corresponds to an unmatched capture in "indices"
+esid: sec-makeindicesarray
+features: [regexp-match-indices]
+info: |
+ MakeIndicesArray ( S, indices, groupNames )
+ 4. Let _n_ be the number of elements in _indices_.
+ ...
+ 6. Set _A_ to ! ArrayCreate(_n_).
+ ...
+ 11. For each integer _i_ such that _i_ >= 0 and _i_ < _n_, do
+ a. Let _matchIndices_ be _indices_[_i_].
+ b. If _matchIndices_ is not *undefined*, then
+ i. Let _matchIndicesArray_ be ! GetMatchIndicesArray(_S_, _matchIndices_).
+ c. Else,
+ i. Let _matchIndicesArray_ be *undefined*.
+ d. Perform ! CreateDataProperty(_A_, ! ToString(_n_), _matchIndicesArray_).
+ ...
+---*/
+
+///
+
+let input = "abd";
+let match = /b(c)?/.exec(input);
+let indices = match.indices;
+
+// `indices` has the same length as match
+assert.sameValue(indices.length, match.length);
+
+// The second element of `indices` should be undefined.
+assert.sameValue(indices[1], undefined);
diff --git a/test/built-ins/RegExp/match-indices/indices-array.js b/test/built-ins/RegExp/match-indices/indices-array.js
new file mode 100644
index 00000000000..67b5a79b335
--- /dev/null
+++ b/test/built-ins/RegExp/match-indices/indices-array.js
@@ -0,0 +1,20 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: The "indices" property is an Array.
+esid: sec-makeindicesarray
+features: [regexp-match-indices]
+info: |
+ MakeIndicesArray ( S, indices, groupNames )
+ 6. Set _A_ to ! ArrayCreate(_n_).
+---*/
+
+///
+
+let match = /a/.exec("a");
+let indices = match.indices;
+
+// `indices` is an array
+assert.sameValue(Object.getPrototypeOf(indices), Array.prototype);
+assert(Array.isArray(indices));
\ No newline at end of file
diff --git a/test/built-ins/RegExp/match-indices/indices-groups-object-undefined.js b/test/built-ins/RegExp/match-indices/indices-groups-object-undefined.js
new file mode 100644
index 00000000000..0fa32f9cf35
--- /dev/null
+++ b/test/built-ins/RegExp/match-indices/indices-groups-object-undefined.js
@@ -0,0 +1,28 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: The groups object of indices is created unconditionally.
+includes: [propertyHelper.js]
+esid: sec-makeindicesarray
+features: [regexp-named-groups]
+info: |
+ MakeIndicesArray ( S, indices, groupNames )
+ 8. If _groupNames_ is not *undefined*, then
+ a. Let _groups_ be ! ObjectCreate(*null*).
+ 9. Else,
+ a. Let _groups_ be *undefined*.
+ 10. Perform ! CreateDataProperty(_A_, `"groups"`, _groups_).
+---*/
+
+///
+///
+
+const re = /./;
+const indices = re.exec("a").indices;
+verifyProperty(indices, 'groups', {
+ writable: true,
+ enumerable: true,
+ configurable: true,
+ value: undefined
+});
diff --git a/test/built-ins/RegExp/match-indices/indices-groups-object-unmatched.js b/test/built-ins/RegExp/match-indices/indices-groups-object-unmatched.js
new file mode 100644
index 00000000000..2ffb3ab85de
--- /dev/null
+++ b/test/built-ins/RegExp/match-indices/indices-groups-object-unmatched.js
@@ -0,0 +1,22 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Test the groups object of indices with matched and unmatched named captures.
+includes: [compareArray.js]
+esid: sec-makeindicesarray
+features: [regexp-named-groups]
+info: |
+ MakeIndicesArray ( S, indices, groupNames )
+ 11. For each integer _i_ such that _i_ >= 0 and _i_ < _n_, do
+ e. If _groupNames_ is not *undfined* and _groupNames_[_i_] is not *undefined*, then
+ i. Perform ! CreateDataProperty(_groups_, _groupNames_[_i_], _matchIndicesArray_).
+---*/
+
+///
+///
+
+const re = /(?a).|(?x)/;
+const result = re.exec("ab").indices;
+assert.compareArray([0, 1], result.groups.a);
+assert.sameValue(undefined, result.groups.x);
diff --git a/test/built-ins/RegExp/match-indices/indices-groups-object.js b/test/built-ins/RegExp/match-indices/indices-groups-object.js
new file mode 100644
index 00000000000..a65e1c2cfd8
--- /dev/null
+++ b/test/built-ins/RegExp/match-indices/indices-groups-object.js
@@ -0,0 +1,43 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: The groups object of indices is created with CreateDataProperty
+includes: [compareArray.js, propertyHelper.js]
+esid: sec-makeindicesarray
+features: [regexp-named-groups]
+info: |
+ MakeIndicesArray ( S, indices, groupNames )
+ 8. If _groupNames_ is not *undefined*, then
+ a. Let _groups_ be ! ObjectCreate(*null*).
+ 9. Else,
+ a. Let _groups_ be *undefined*.
+ 10. Perform ! CreateDataProperty(_A_, `"groups"`, _groups_).
+---*/
+
+///
+///
+///
+
+// `groups` is created with Define, not Set.
+let counter = 0;
+Object.defineProperty(Array.prototype, "groups", {
+ set() { counter++; }
+});
+
+let indices = /(?.)/.exec("a").indices;
+assert.sameValue(counter, 0);
+
+// `groups` is writable, enumerable and configurable
+// (from CreateDataProperty).
+verifyProperty(indices, 'groups', {
+ writable: true,
+ enumerable: true,
+ configurable: true
+});
+
+// The `__proto__` property on the groups object is not special,
+// and does not affect the [[Prototype]] of the resulting groups object.
+let {groups} = /(?<__proto__>.)/.exec("a").indices;
+assert.compareArray([0, 1], groups.__proto__);
+assert.sameValue(null, Object.getPrototypeOf(groups));
diff --git a/test/built-ins/RegExp/match-indices/indices-groups-properties.js b/test/built-ins/RegExp/match-indices/indices-groups-properties.js
new file mode 100644
index 00000000000..484b8fdb4a5
--- /dev/null
+++ b/test/built-ins/RegExp/match-indices/indices-groups-properties.js
@@ -0,0 +1,38 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Properties of the groups object of indices are created with CreateDataProperty
+includes: [compareArray.js, propertyHelper.js]
+esid: sec-makeindicesarray
+features: [regexp-named-groups]
+info: |
+ MakeIndicesArray ( S, indices, groupNames )
+ 11. For each integer _i_ such that _i_ >= 0 and _i_ < _n_, do
+ e. If _groupNames_ is not *undfined* and _groupNames_[_i_] is not *undefined*, then
+ i. Perform ! CreateDataProperty(_groups_, _groupNames_[_i_], _matchIndicesArray_).
+---*/
+
+///
+///
+///
+
+// Properties created on result.groups in textual order.
+let groupNames = Object.getOwnPropertyNames(/(?.)|(?.)/u.exec("abcd").indices.groups);
+assert.compareArray(groupNames, ["fst", "snd"]);
+
+// // Properties are created with Define, not Set
+// let counter = 0;
+// Object.defineProperty(Object.prototype, 'x', {set() { counter++; }});
+
+let indices = /(?.)/.exec('a').indices;
+let groups = indices.groups;
+// assert.sameValue(counter, 0);
+
+// Properties are writable, enumerable and configurable
+// (from CreateDataProperty)
+verifyProperty(groups, 'x', {
+ writable: true,
+ enumerable: true,
+ configurable: true
+});
diff --git a/test/built-ins/RegExp/match-indices/indices-property.js b/test/built-ins/RegExp/match-indices/indices-property.js
new file mode 100644
index 00000000000..4fd66b8c40e
--- /dev/null
+++ b/test/built-ins/RegExp/match-indices/indices-property.js
@@ -0,0 +1,32 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: The "indices" property is created with DefinePropertyOrThrow
+includes: [propertyHelper.js]
+esid: sec-regexpbuiltinexec
+features: [regexp-match-indices]
+info: |
+ Runtime Semantics: RegExpBuiltinExec ( R, S )
+ 34. Let _indicesArray_ be MakeIndicesArray(_S_, _indices_, _groupNames_).
+ 35. Perform ! DefinePropertyOrThrow(_A_, `"indices"`, PropertyDescriptor { [[Value]]: _indicesArray_, [[Writable]]: *false*, [[Enumerable]]: *false*, [[Configurable]]: *true* }).
+---*/
+
+///
+///
+
+// `indices` is created with Define, not Set.
+let counter = 0;
+Object.defineProperty(Array.prototype, "indices", {
+ set() { counter++; }
+});
+
+let match = /a/.exec("a");
+assert.sameValue(counter, 0);
+
+// `indices` is a non-writable, non-enumerable, and configurable data-property.
+verifyProperty(match, 'indices', {
+ writable: true,
+ enumerable: true,
+ configurable: true
+});
diff --git a/test/harness/deepEqual-array.js b/test/harness/deepEqual-array.js
new file mode 100644
index 00000000000..06d3419c48c
--- /dev/null
+++ b/test/harness/deepEqual-array.js
@@ -0,0 +1,17 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ array values compare correctly.
+includes: [deepEqual.js]
+---*/
+
+///
+///
+
+assert.deepEqual([], []);
+assert.deepEqual([1, "a", true], [1, "a", true]);
+
+assert.throws(Test262Error, function () { assert.deepEqual([], [1]); });
+assert.throws(Test262Error, function () { assert.deepEqual([1, "a", true], [1, "a", false]); });
diff --git a/test/harness/deepEqual-circular.js b/test/harness/deepEqual-circular.js
new file mode 100644
index 00000000000..d48420f0dbc
--- /dev/null
+++ b/test/harness/deepEqual-circular.js
@@ -0,0 +1,20 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ values compare correctly with circular references.
+includes: [deepEqual.js]
+---*/
+
+///
+///
+
+var a = { x: 1 };
+var b = { x: 1 };
+a.a = a;
+a.b = b;
+b.a = b;
+b.b = a;
+
+assert.deepEqual(a, b);
diff --git a/test/harness/deepEqual-deep.js b/test/harness/deepEqual-deep.js
new file mode 100644
index 00000000000..c14829df1f1
--- /dev/null
+++ b/test/harness/deepEqual-deep.js
@@ -0,0 +1,16 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ values compare correctly.
+includes: [deepEqual.js]
+---*/
+
+///
+///
+
+assert.deepEqual({ a: { x: 1 }, b: [true] }, { a: { x: 1 }, b: [true] });
+
+assert.throws(Test262Error, function () { assert.deepEqual({}, { a: { x: 1 }, b: [true] }); });
+assert.throws(Test262Error, function () { assert.deepEqual({ a: { x: 1 }, b: [true] }, { a: { x: 1 }, b: [false] }); });
diff --git a/test/harness/deepEqual-mapset.js b/test/harness/deepEqual-mapset.js
new file mode 100644
index 00000000000..1473097ef72
--- /dev/null
+++ b/test/harness/deepEqual-mapset.js
@@ -0,0 +1,22 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ map/set values compare correctly.
+includes: [deepEqual.js]
+---*/
+
+///
+///
+
+assert.deepEqual(new Set(), new Set());
+assert.deepEqual(new Set([1, "a", true]), new Set([1, "a", true]));
+assert.deepEqual(new Map(), new Map());
+assert.deepEqual(new Map([[1, "a"], ["b", true]]), new Map([[1, "a"], ["b", true]]));
+
+assert.throws(Test262Error, function () { assert.deepEqual(new Set([]), new Set([1])); });
+assert.throws(Test262Error, function () { assert.deepEqual(new Set([1, "a", true]), new Set([1, "a", false])); });
+assert.throws(Test262Error, function () { assert.deepEqual(new Map([]), new Map([[1, "a"], ["b", true]])); });
+assert.throws(Test262Error, function () { assert.deepEqual(new Map([[1, "a"], ["b", true]]), new Map([[1, "a"], ["b", false]])); });
+assert.throws(Test262Error, function () { assert.deepEqual(new Map([[1, "a"], ["b", true]]), new Set([[1, "a"], ["b", false]])); });
diff --git a/test/harness/deepEqual-object.js b/test/harness/deepEqual-object.js
new file mode 100644
index 00000000000..0dac2b682e5
--- /dev/null
+++ b/test/harness/deepEqual-object.js
@@ -0,0 +1,17 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ object values compare correctly.
+includes: [deepEqual.js]
+---*/
+
+///
+///
+
+assert.deepEqual({}, {});
+assert.deepEqual({ a: 1, b: true }, { a: 1, b: true });
+
+assert.throws(Test262Error, function () { assert.deepEqual({}, { a: 1, b: true }); });
+assert.throws(Test262Error, function () { assert.deepEqual({ a: 1, b: true }, { a: 1, b: false }); });
diff --git a/test/harness/deepEqual-primitives.js b/test/harness/deepEqual-primitives.js
new file mode 100644
index 00000000000..8249d22304c
--- /dev/null
+++ b/test/harness/deepEqual-primitives.js
@@ -0,0 +1,38 @@
+// Copyright 2019 Ron Buckton. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+ primitive values compare correctly.
+includes: [deepEqual.js]
+---*/
+
+///
+///
+
+var s1 = Symbol();
+var s2 = Symbol();
+assert.deepEqual(null, null);
+assert.deepEqual(undefined, undefined);
+assert.deepEqual("a", "a");
+assert.deepEqual(1, 1);
+assert.deepEqual(1n, 1n);
+assert.deepEqual(true, true);
+assert.deepEqual(s1, s1);
+assert.deepEqual(Object("a"), "a");
+assert.deepEqual(Object(1), 1);
+assert.deepEqual(Object(1n), 1n);
+assert.deepEqual(Object(true), true);
+assert.deepEqual(Object(s1), s1);
+
+assert.throws(Test262Error, function () { assert.deepEqual(null, 0); });
+assert.throws(Test262Error, function () { assert.deepEqual(undefined, 0); });
+assert.throws(Test262Error, function () { assert.deepEqual("", 0); });
+assert.throws(Test262Error, function () { assert.deepEqual("1", 1); });
+assert.throws(Test262Error, function () { assert.deepEqual("1", "2"); });
+assert.throws(Test262Error, function () { assert.deepEqual(1n, 1); });
+assert.throws(Test262Error, function () { assert.deepEqual(1n, 2n); });
+assert.throws(Test262Error, function () { assert.deepEqual(true, 1); });
+assert.throws(Test262Error, function () { assert.deepEqual(true, false); });
+assert.throws(Test262Error, function () { assert.deepEqual(s1, "Symbol()"); });
+assert.throws(Test262Error, function () { assert.deepEqual(s1, s2); });