backport Iterator.prototype.includes

This commit is contained in:
Denis Pushkarev
2026-06-27 11:17:45 +07:00
parent 63481ce2f9
commit 6c756fa1de
22 changed files with 268 additions and 0 deletions
+3
View File
@@ -12,6 +12,9 @@
- `Iterator.prototype.windows`
- Moved to stage 3, [May 2026 TC39 meeting](https://github.com/tc39/proposals/commit/7226f0af93443025010efcad219115cec7dc8b2a)
- Added `/actual/` namespaces entries, unconditional forced replacement changed to feature detection
- [`Iterator` includes stage 3 proposal](https://github.com/tc39/proposal-iterator-includes):
- Added built-in:
- `Iterator.prototype.includes`
- Ensure opaque paths always roundtrip in URL polyfill (still without adding to feature detection), [whatwg/url#844](https://github.com/whatwg/url/pull/844)
- Fixed possible crash on some keys in `Symbol.for`
- Some `get-iterator` / `get-iterator-method` fixes
+21
View File
@@ -173,6 +173,7 @@ structuredClone(new Set([1, 2, 3])); // => new Set([1, 2, 3])
- [`Math.sumPrecise`](#mathsumprecise)
- [Stage 3 proposals](#stage-3-proposals)
- [`Iterator` chunking](#iterator-chunking)
- [`Iterator` includes](#iterator-includes)
- [Stage 2.7 proposals](#stage-27-proposals)
- [`Symbol.metadata` for decorators metadata proposal](#symbolmetadata-for-decorators-metadata-proposal)
- [Stage 2 proposals](#stage-2-proposals)
@@ -2901,6 +2902,26 @@ let windowsPartial = Array.from([0, 1].values().windows(3, 'allow-partial')); /
let windowsFull = Array.from([0, 1].values().windows(3)); // []
```
##### [`Iterator` includes](https://github.com/tc39/proposal-iterator-includes)[⬆](#index)
Module [`esnext.iterator.includes`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.iterator.includes.js)
```ts
class Iterator {
includes(searchElement: any, skippedElements?: number): boolean;
}
```
[*CommonJS entry points:*](#commonjs-api)
```
core-js/proposals/iterator-includes
core-js(-pure)/actual|full/iterator/includes
```
```js
[1, 2, 3].values().includes(2); // => true
[1, 2, 3].values().includes(4); // => false
[NaN].values().includes(NaN); // => true
[1, 2, 3].values().includes(3, 2); // => true
[1, 2, 3].values().includes(1, 1); // => false
```
#### Stage 2.7 proposals[⬆](#index)
[*CommonJS entry points:*](#commonjs-api)
```
@@ -0,0 +1,28 @@
# Iterator includes
[Specification](https://tc39.es/proposal-iterator-includes/)\
[Proposal repo](https://github.com/tc39/proposal-iterator-includes)
## Modules
[`esnext.iterator.includes`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.iterator.includes.js)
## Built-ins signatures
```ts
class Iterator {
includes(searchElement: any, skippedElements?: number): boolean;
}
```
## [Entry points]({docs-version}/docs/usage#h-entry-points)
```plaintext
core-js/proposals/iterator-includes
core-js(-pure)/actual|full/iterator/includes
```
## Examples
```js
[1, 2, 3].values().includes(2); // => true
[1, 2, 3].values().includes(4); // => false
[NaN].values().includes(NaN); // => true
[1, 2, 3].values().includes(3, 2); // => true
[1, 2, 3].values().includes(1, 1); // => false
```
+4
View File
@@ -257,6 +257,10 @@
{
"title": "Iterator chunking",
"url": "{docs-version}/docs/features/proposals/iterator-chunking"
},
{
"title": "Iterator includes",
"url": "{docs-version}/docs/features/proposals/iterator-includes"
}
]
},
+2
View File
@@ -2632,6 +2632,8 @@ export const data = {
'esnext.iterator.for-each': null,
// TODO: Remove from `core-js@4`
'esnext.iterator.from': null,
'esnext.iterator.includes': {
},
'esnext.iterator.indexed': {
},
// TODO: Remove from `core-js@4`
@@ -326,5 +326,6 @@ export default {
'3.50': [
'es.iterator.zip',
'es.iterator.zip-keyed',
'esnext.iterator.includes',
],
};
@@ -2,6 +2,7 @@
var parent = require('../../stable/iterator/concat');
require('../../modules/esnext.iterator.chunks');
require('../../modules/esnext.iterator.concat');
require('../../modules/esnext.iterator.includes');
require('../../modules/esnext.iterator.windows');
module.exports = parent;
+1
View File
@@ -11,6 +11,7 @@ require('../../modules/esnext.iterator.find');
require('../../modules/esnext.iterator.flat-map');
require('../../modules/esnext.iterator.for-each');
require('../../modules/esnext.iterator.from');
require('../../modules/esnext.iterator.includes');
require('../../modules/esnext.iterator.map');
require('../../modules/esnext.iterator.reduce');
require('../../modules/esnext.iterator.some');
@@ -0,0 +1,8 @@
'use strict';
require('../../modules/es.object.to-string');
require('../../modules/es.iterator.constructor');
require('../../modules/esnext.iterator.includes');
var entryUnbind = require('../../internals/entry-unbind');
module.exports = entryUnbind('Iterator', 'includes');
@@ -13,6 +13,7 @@ require('../../modules/esnext.iterator.find');
require('../../modules/esnext.iterator.flat-map');
require('../../modules/esnext.iterator.for-each');
require('../../modules/esnext.iterator.from');
require('../../modules/esnext.iterator.includes');
require('../../modules/esnext.iterator.map');
require('../../modules/esnext.iterator.reduce');
require('../../modules/esnext.iterator.some');
@@ -1,6 +1,7 @@
'use strict';
var parent = require('../../stable/iterator/zip');
require('../../modules/esnext.iterator.chunks');
require('../../modules/esnext.iterator.includes');
require('../../modules/esnext.iterator.windows');
require('../../modules/esnext.iterator.zip-keyed');
+1
View File
@@ -1,6 +1,7 @@
'use strict';
var parent = require('../../stable/iterator/zip');
require('../../modules/esnext.iterator.chunks');
require('../../modules/esnext.iterator.includes');
require('../../modules/esnext.iterator.windows');
require('../../modules/esnext.iterator.zip');
@@ -0,0 +1,4 @@
'use strict';
var parent = require('../../actual/iterator/includes');
module.exports = parent;
+1
View File
@@ -17,6 +17,7 @@ require('../../modules/es.iterator.to-array');
// TODO: drop from core-js@4
require('../../modules/esnext.iterator.constructor');
require('../../modules/esnext.iterator.chunks');
require('../../modules/esnext.iterator.includes');
require('../../modules/esnext.iterator.range');
require('../../modules/esnext.iterator.sliding');
require('../../modules/esnext.iterator.windows');
@@ -0,0 +1,42 @@
'use strict';
var $ = require('../internals/export');
var anObject = require('../internals/an-object');
var isIntegralNumber = require('../internals/is-integral-number');
var iterate = require('../internals/iterate');
var iteratorClose = require('../internals/iterator-close');
var sameValueZero = require('../internals/same-value-zero');
var IS_PURE = require('../internals/is-pure');
var $RangeError = RangeError;
var $TypeError = TypeError;
var $Infinity = Infinity;
var INVALID_SKIPPED_ELEMENTS = 'skippedElements should be a positive integer or Infinity';
// `Iterator.prototype.includes` method
// https://github.com/tc39/proposal-iterator-includes
$({ target: 'Iterator', proto: true, real: true, forced: IS_PURE }, {
includes: function includes(searchElement /* , skippedElements */) {
anObject(this);
var skippedElements = arguments.length > 1 ? arguments[1] : undefined;
var skipped = 0;
var toSkip;
try {
if (skippedElements === undefined) {
toSkip = 0;
} else if (skippedElements === $Infinity || skippedElements === -$Infinity || isIntegralNumber(skippedElements)) {
toSkip = skippedElements;
} else {
throw new $TypeError(INVALID_SKIPPED_ELEMENTS);
}
if (toSkip < 0) throw new $RangeError(INVALID_SKIPPED_ELEMENTS);
} catch (error) {
iteratorClose(this, 'throw', error);
}
return iterate(this, function (value, stop) {
if (skipped < toSkip) skipped++;
else if (sameValueZero(value, searchElement)) return stop();
}, { IS_ITERATOR: true, INTERRUPTED: true }).stopped;
}
});
@@ -0,0 +1,3 @@
'use strict';
// https://github.com/tc39/proposal-iterator-includes
require('../modules/esnext.iterator.includes');
+1
View File
@@ -2,6 +2,7 @@
var parent = require('./4');
require('../proposals/decorator-metadata-v2');
require('../proposals/iterator-includes');
require('../proposals/joint-iteration');
// TODO: Obsolete versions, remove from `core-js@4`
require('../proposals/array-grouping-stage-3');
+3
View File
@@ -1955,6 +1955,9 @@ GLOBAL.tests = {
'esnext.iterator.chunks': function () {
return Iterator.prototype.chunks;
},
'esnext.iterator.includes': function () {
return Iterator.prototype.includes;
},
'esnext.iterator.range': function () {
return Iterator.range;
},
+2
View File
@@ -721,6 +721,7 @@ for (PATH of ['core-js-pure', 'core-js']) {
ok(typeof load(NS, 'async-iterator/to-array') == 'function');
ok(load(NS, 'function/metadata') === null);
ok(typeof load(NS, 'iterator/chunks') == 'function');
ok(load(NS, 'iterator/includes')([1, 2].values(), 2) === true);
ok(typeof load(NS, 'iterator/to-async') == 'function');
ok(typeof load(NS, 'iterator/windows') == 'function');
ok(load(NS, 'symbol/metadata'));
@@ -956,6 +957,7 @@ for (PATH of ['core-js-pure', 'core-js']) {
load('proposals/iterator-sequencing');
load('proposals/iterator-chunking');
load('proposals/iterator-chunking-v2');
load('proposals/iterator-includes');
load('proposals/joint-iteration');
load('proposals/json-parse-with-source');
load('proposals/keys-composition');
+1
View File
@@ -1300,6 +1300,7 @@ const forbidCompletelyNonExistentBuiltIns = {
] }],
'es/no-nonstandard-iterator-prototype-properties': [ERROR, { allow: [
'chunks',
'includes',
'sliding',
'toAsync',
'windows',
@@ -0,0 +1,69 @@
import { STRICT } from '../helpers/constants.js';
import { createIterator } from '../helpers/helpers.js';
QUnit.test('Iterator#includes', assert => {
const { includes } = Iterator.prototype;
assert.isFunction(includes);
assert.arity(includes, 1);
assert.name(includes, 'includes');
assert.looksNative(includes);
assert.nonEnumerable(Iterator.prototype, 'includes');
assert.true(includes.call(createIterator([1, 2, 3]), 2), 'basic functionality #1');
assert.false(includes.call(createIterator([1, 2, 3]), 4), 'basic functionality #2');
assert.true(includes.call(createIterator([1, 'foo', 3]), 'foo'), 'basic functionality #3');
assert.false(includes.call(createIterator([]), 1), 'empty iterator');
// SameValueZero semantics
assert.true(includes.call(createIterator([NaN]), NaN), 'NaN is found');
assert.true(includes.call(createIterator([0]), -0), '+0 and -0 are equal #1');
assert.true(includes.call(createIterator([-0]), 0), '+0 and -0 are equal #2');
assert.false(includes.call(createIterator([undefined]), null), 'undefined is not null');
// skippedElements parameter - valid values
assert.true(includes.call(createIterator([1, 2, 3]), 3, 2), 'skippedElements #1');
assert.false(includes.call(createIterator([1, 2, 3]), 1, 1), 'skippedElements #2');
assert.true(includes.call(createIterator([1, 2, 3]), 1, 0), 'skippedElements 0');
assert.true(includes.call(createIterator([1, 2, 3]), 1, -0), 'skippedElements -0');
assert.true(includes.call(createIterator([1, 2, 3]), 1, undefined), 'skippedElements undefined');
assert.false(includes.call(createIterator([1, 2, 3]), 1, Infinity), 'skippedElements Infinity');
assert.true(includes.call(createIterator([1, 2, 3, 2]), 2, 2), 'skippedElements finds later occurrence');
assert.false(includes.call(createIterator([1, 2, 3]), 2, 3), 'skippedElements skips all');
// skippedElements parameter - invalid values (must be integral Number, +Infinity, or -Infinity)
assert.throws(() => includes.call(createIterator([1]), 1, 1.5), TypeError, 'non-integer number');
assert.throws(() => includes.call(createIterator([1]), 1, NaN), TypeError, 'NaN');
assert.throws(() => includes.call(createIterator([1]), 1, '1'), TypeError, 'string');
assert.throws(() => includes.call(createIterator([1]), 1, true), TypeError, 'boolean');
assert.throws(() => includes.call(createIterator([1]), 1, null), TypeError, 'null');
assert.throws(() => includes.call(createIterator([1]), 1, {}), TypeError, 'object');
// skippedElements - negative values
assert.throws(() => includes.call(createIterator([1]), 1, -1), RangeError, 'negative integer');
assert.throws(() => includes.call(createIterator([1]), 1, -Infinity), RangeError, 'negative Infinity');
// iterator closing on match
const it1 = createIterator([1, 2, 3], {
return() {
this.closed = true;
return { done: true, value: undefined };
},
});
assert.true(includes.call(it1, 2), 'found element');
assert.true(it1.closed, 'iterator closes on match');
// iterator closing on skippedElements validation error
const it2 = createIterator([1, 2, 3], { return() { this.closed = true; } });
assert.throws(() => includes.call(it2, 1, -1), RangeError, 'negative skippedElements');
assert.true(it2.closed, 'iterator closes on negative skippedElements');
const it3 = createIterator([1, 2, 3], { return() { this.closed = true; } });
assert.throws(() => includes.call(it3, 1, 1.5), TypeError, 'non-integer skippedElements');
assert.true(it3.closed, 'iterator closes on non-integer skippedElements');
if (STRICT) {
assert.throws(() => includes.call(undefined, 1), TypeError, 'non-object this #1');
assert.throws(() => includes.call(null, 1), TypeError, 'non-object this #2');
}
});
@@ -0,0 +1,70 @@
import { STRICT } from '../helpers/constants.js';
import { createIterator } from '../helpers/helpers.js';
import Iterator from 'core-js-pure/full/iterator';
QUnit.test('Iterator#includes', assert => {
const { includes } = Iterator.prototype;
assert.isFunction(includes);
assert.arity(includes, 1);
assert.name(includes, 'includes');
assert.nonEnumerable(Iterator.prototype, 'includes');
assert.true(includes.call(createIterator([1, 2, 3]), 2), 'basic functionality #1');
assert.false(includes.call(createIterator([1, 2, 3]), 4), 'basic functionality #2');
assert.true(includes.call(createIterator([1, 'foo', 3]), 'foo'), 'basic functionality #3');
assert.false(includes.call(createIterator([]), 1), 'empty iterator');
// SameValueZero semantics
assert.true(includes.call(createIterator([NaN]), NaN), 'NaN is found');
assert.true(includes.call(createIterator([0]), -0), '+0 and -0 are equal #1');
assert.true(includes.call(createIterator([-0]), 0), '+0 and -0 are equal #2');
assert.false(includes.call(createIterator([undefined]), null), 'undefined is not null');
// skippedElements parameter - valid values
assert.true(includes.call(createIterator([1, 2, 3]), 3, 2), 'skippedElements #1');
assert.false(includes.call(createIterator([1, 2, 3]), 1, 1), 'skippedElements #2');
assert.true(includes.call(createIterator([1, 2, 3]), 1, 0), 'skippedElements 0');
assert.true(includes.call(createIterator([1, 2, 3]), 1, -0), 'skippedElements -0');
assert.true(includes.call(createIterator([1, 2, 3]), 1, undefined), 'skippedElements undefined');
assert.false(includes.call(createIterator([1, 2, 3]), 1, Infinity), 'skippedElements Infinity');
assert.true(includes.call(createIterator([1, 2, 3, 2]), 2, 2), 'skippedElements finds later occurrence');
assert.false(includes.call(createIterator([1, 2, 3]), 2, 3), 'skippedElements skips all');
// skippedElements parameter - invalid values (must be integral Number, +Infinity, or -Infinity)
assert.throws(() => includes.call(createIterator([1]), 1, 1.5), TypeError, 'non-integer number');
assert.throws(() => includes.call(createIterator([1]), 1, NaN), TypeError, 'NaN');
assert.throws(() => includes.call(createIterator([1]), 1, '1'), TypeError, 'string');
assert.throws(() => includes.call(createIterator([1]), 1, true), TypeError, 'boolean');
assert.throws(() => includes.call(createIterator([1]), 1, null), TypeError, 'null');
assert.throws(() => includes.call(createIterator([1]), 1, {}), TypeError, 'object');
// skippedElements - negative values
assert.throws(() => includes.call(createIterator([1]), 1, -1), RangeError, 'negative integer');
assert.throws(() => includes.call(createIterator([1]), 1, -Infinity), RangeError, 'negative Infinity');
// iterator closing on match
const it1 = createIterator([1, 2, 3], {
return() {
this.closed = true;
return { done: true, value: undefined };
},
});
assert.true(includes.call(it1, 2), 'found element');
assert.true(it1.closed, 'iterator closes on match');
// iterator closing on skippedElements validation error
const it2 = createIterator([1, 2, 3], { return() { this.closed = true; } });
assert.throws(() => includes.call(it2, 1, -1), RangeError, 'negative skippedElements');
assert.true(it2.closed, 'iterator closes on negative skippedElements');
const it3 = createIterator([1, 2, 3], { return() { this.closed = true; } });
assert.throws(() => includes.call(it3, 1, 1.5), TypeError, 'non-integer skippedElements');
assert.true(it3.closed, 'iterator closes on non-integer skippedElements');
if (STRICT) {
assert.throws(() => includes.call(undefined, 1), TypeError, 'non-object this #1');
assert.throws(() => includes.call(null, 1), TypeError, 'non-object this #2');
}
});