Skip to content

Commit 46c07c2

Browse files
accept a second parameter
1 parent 16a6f09 commit 46c07c2

4 files changed

Lines changed: 135 additions & 9 deletions

File tree

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,14 @@ stronger than choosing SameValueZero to match `Array.prototype.includes`.
3939
`Array.prototype.includes` has a second parameter that starts the search from
4040
the given index instead of the beginning of the Array. This makes sense for the
4141
Array API because the alternative (slicing first) would first allocate another
42-
Array and then perform a copy from that index. But Iterators have `drop` which
43-
is a constant time/space operation, so there's no need to include this
44-
parameter. That being said, it *could* be included to mirror the Array API, but
45-
I don't think it's worth it. Additionally, if it was included, it would have to
46-
reject negative values, which would be an unnecessarily surprising difference.
42+
Array and then perform a copy from that index. Iterators have `drop`, so it's
43+
unnecessary to include this parameter, but to avoid confusion for somebody who
44+
is already familiar with the Array method, we choose to include it.
45+
Unfortunately, there is still a difference in the interfaces because the
46+
Iterator method cannot accept negative offsets, which would be a surprising
47+
difference, but one that makes sense in context. Alternatively, we could throw
48+
if a second argument is ever provided, but that would be a highly unusual
49+
behaviour among JavaScript built-ins.
4750

4851
## chosen solution
4952

spec.emu

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,24 @@ copyright: false
1010
</pre>
1111

1212
<emu-clause id="sec-iterator.prototype.includes">
13-
<h1>Iterator.prototype.includes ( _searchElement_ )</h1>
13+
<h1>Iterator.prototype.includes ( _searchElement_ [ , _skippedElements_ ] )</h1>
1414
<emu-alg>
1515
1. Let _O_ be the *this* value.
1616
1. If _O_ is not an Object, throw a *TypeError* exception.
17+
1. If _skippedElements_ is *undefined*, then
18+
1. Let _toSkip_ be 0.
19+
1. Else,
20+
1. If _skippedElements_ is not one of *+∞<sub>𝔽</sub>*, *-∞<sub>𝔽</sub>*, or an integral Number, throw a *TypeError* exception.
21+
1. Let _toSkip_ be the extended mathematical value of _skippedElements_.
22+
1. If _toSkip_ &lt; 0, throw a *RangeError* exception.
23+
1. Let _skipped_ be 0.
1724
1. Let _iterated_ be ? GetIteratorDirect(_O_).
1825
1. Repeat,
1926
1. Let _value_ be ? IteratorStepValue(_iterated_).
2027
1. If _value_ is ~done~, return *false*.
21-
1. If SameValueZero(_value_, _searchElement_) is *true*, return ? IteratorClose(_iterated_, NormalCompletion(*true*)).
28+
1. If _skipped_ &lt; _toSkip_, then
29+
1. Set _skipped_ to _skipped_ + 1.
30+
1. Else,
31+
1. If SameValueZero(_value_, _searchElement_) is *true*, return ? IteratorClose(_iterated_, NormalCompletion(*true*)).
2232
</emu-alg>
2333
</emu-clause>

src/index.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
1-
function includes<T>(this: IterableIterator<T>, searchElement: T): boolean {
1+
function includes<T>(this: IterableIterator<T>, searchElement: T, skippedElements = undefined): boolean {
2+
let toSkip = 0;
3+
if (skippedElements !== undefined) {
4+
if (!(skippedElements === 2e308 || skippedElements === -2e308 || typeof skippedElements === 'number' && Math.trunc(skippedElements) === skippedElements)) {
5+
throw new TypeError;
6+
}
7+
toSkip = skippedElements as number;
8+
}
9+
if (toSkip < 0) {
10+
throw new RangeError;
11+
}
12+
let skipped = 0;
213
for (let e of this) {
3-
if ([e].includes(searchElement)) return true;
14+
if (skipped < toSkip) {
15+
++skipped;
16+
} else if ([e].includes(searchElement)) {
17+
return true;
18+
}
419
}
520
return false;
621
}

test/index.mjs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,104 @@ test('closes iterator', async t => {
7575
assert.equal(closed, true);
7676
});
7777

78+
test('skipped elements', async t => {
79+
await test('negative integral', async t => {
80+
assert.throws(() => {
81+
[].values().includes(0, -1);
82+
}, RangeError);
83+
});
84+
85+
await test('negative non-integral', async t => {
86+
assert.throws(() => {
87+
[].values().includes(0, -0.1);
88+
}, TypeError);
89+
});
90+
91+
await test('negative infinity', async t => {
92+
assert.throws(() => {
93+
[].values().includes(0, -2e308);
94+
}, RangeError);
95+
});
96+
97+
await test('zero', async t => {
98+
assert.equal([4, 5, 6, 7].values().includes(8, 0), false);
99+
assert.equal([4, 5, 6, 7].values().includes(7, 0), true);
100+
assert.equal([4, 5, 6, 7].values().includes(6, 0), true);
101+
assert.equal([4, 5, 6, 7].values().includes(5, 0), true);
102+
assert.equal([4, 5, 6, 7].values().includes(4, 0), true);
103+
assert.equal([4, 5, 6, 7].values().includes(3, 0), false);
104+
105+
assert.equal([4, 5, 6, 7].values().includes(8, -0), false);
106+
assert.equal([4, 5, 6, 7].values().includes(7, -0), true);
107+
assert.equal([4, 5, 6, 7].values().includes(6, -0), true);
108+
assert.equal([4, 5, 6, 7].values().includes(5, -0), true);
109+
assert.equal([4, 5, 6, 7].values().includes(4, -0), true);
110+
assert.equal([4, 5, 6, 7].values().includes(3, -0), false);
111+
});
112+
113+
await test('positive integral', async t => {
114+
assert.equal([4, 5, 6, 7].values().includes(4, 1), false);
115+
assert.equal([4, 5, 6, 7].values().includes(4, 2), false);
116+
assert.equal([4, 5, 6, 7].values().includes(4, 3), false);
117+
assert.equal([4, 5, 6, 7].values().includes(4, 4), false);
118+
assert.equal([4, 5, 6, 7].values().includes(4, 5), false);
119+
120+
assert.equal([4, 5, 6, 7].values().includes(5, 1), true);
121+
assert.equal([4, 5, 6, 7].values().includes(5, 2), false);
122+
assert.equal([4, 5, 6, 7].values().includes(5, 3), false);
123+
assert.equal([4, 5, 6, 7].values().includes(5, 4), false);
124+
assert.equal([4, 5, 6, 7].values().includes(5, 5), false);
125+
126+
assert.equal([4, 5, 6, 7].values().includes(6, 1), true);
127+
assert.equal([4, 5, 6, 7].values().includes(6, 2), true);
128+
assert.equal([4, 5, 6, 7].values().includes(6, 3), false);
129+
assert.equal([4, 5, 6, 7].values().includes(6, 4), false);
130+
assert.equal([4, 5, 6, 7].values().includes(6, 5), false);
131+
132+
assert.equal([4, 5, 6, 7].values().includes(7, 1), true);
133+
assert.equal([4, 5, 6, 7].values().includes(7, 2), true);
134+
assert.equal([4, 5, 6, 7].values().includes(7, 3), true);
135+
assert.equal([4, 5, 6, 7].values().includes(7, 4), false);
136+
assert.equal([4, 5, 6, 7].values().includes(7, 5), false);
137+
});
138+
139+
await test('positive non-integral', async t => {
140+
assert.throws(() => {
141+
[].values().includes(0, 0.1);
142+
}, TypeError);
143+
});
144+
145+
await test('positive infinity', async t => {
146+
let closed = false;
147+
let i = 0;
148+
let iter = {
149+
__proto__: Iterator.prototype,
150+
next() {
151+
++i;
152+
if (i < 1000) {
153+
return { value: i, done: false };
154+
} else {
155+
closed = true;
156+
return { value: undefined, done: true };
157+
}
158+
},
159+
return() {
160+
closed = true;
161+
return { value: undefined, done: true };
162+
},
163+
};
164+
165+
assert.equal(iter.includes(1, Infinity), false);
166+
assert.equal(closed, true);
167+
});
168+
169+
await test('non-numeric', async t => {
170+
assert.throws(() => {
171+
[].values().includes(0, { valueOf() { return 0; } });
172+
}, TypeError);
173+
});
174+
});
175+
78176
test('name', async t => {
79177
assert.equal(Iterator.prototype.includes.name, 'includes');
80178
});

0 commit comments

Comments
 (0)