diff --git a/polyfills/Iterator/prototype/chunks/config.toml b/polyfills/Iterator/prototype/chunks/config.toml new file mode 100644 index 000000000..f2fc233f5 --- /dev/null +++ b/polyfills/Iterator/prototype/chunks/config.toml @@ -0,0 +1,33 @@ +aliases = [ "es2027" ] +dependencies = [ + "_ESAbstract.CreateIterResultObject", + "_ESAbstract.CreateMethodProperty", + "_ESAbstract.Get", + "_ESAbstract.IsInteger", + "_ESAbstract.IteratorClose", + "_ESAbstract.IteratorStepValue", + "_ESAbstract.NormalCompletion", + "_ESAbstract.ThrowCompletion", + "_ESAbstract.Type", + "Iterator", + "Iterator.prototype.take", + "Symbol.iterator", +] +spec = "https://tc39.es/ecma262/#sec-iterator.prototype.chunks" +docs = "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/chunks" + +[browsers] +android = "*" +bb = "*" +chrome = "*" +edge = "*" +edge_mob = "*" +firefox = "<154" +firefox_mob = "<154" +ie = "*" +ie_mob = "*" +opera = "*" +op_mob = "*" +safari = "*" +ios_saf = "*" +samsung_mob = "*" diff --git a/polyfills/Iterator/prototype/chunks/detect.js b/polyfills/Iterator/prototype/chunks/detect.js new file mode 100644 index 000000000..79e2b91e1 --- /dev/null +++ b/polyfills/Iterator/prototype/chunks/detect.js @@ -0,0 +1 @@ +"Iterator" in self && "chunks" in self.Iterator.prototype; diff --git a/polyfills/Iterator/prototype/chunks/polyfill.js b/polyfills/Iterator/prototype/chunks/polyfill.js new file mode 100644 index 000000000..b63aca329 --- /dev/null +++ b/polyfills/Iterator/prototype/chunks/polyfill.js @@ -0,0 +1,115 @@ +/* global CreateIterResultObject, CreateMethodProperty, Get, IsInteger, Iterator, IteratorClose, IteratorStepValue, NormalCompletion, Symbol, ThrowCompletion, Type */ + +var IteratorHelperPrototype = (function () { + var iterator = Object.create(Iterator.prototype); + iterator[Symbol.iterator] = function () { + return { + next: function () { + return { value: undefined, done: true }; + } + }; + }; + // use `Iterator.prototype.take` as a way to get `IteratorHelperPrototype` + var iteratorHelper = iterator.take(0); + return Object.getPrototypeOf(iteratorHelper); +})(); + +// 27.1.3.3.1 Iterator.prototype.chunks ( chunkSize ) +CreateMethodProperty(Iterator.prototype, "chunks", function chunks(chunkSize) { + // 1. Let obj be the this value. + var obj = this; + // 2. If obj is not an Object, throw a TypeError exception. + if (Type(obj) !== "object") { + throw new TypeError("`this` is not an object"); + } + // 3. Let iterated be the Iterator Record { [[Iterator]]: obj, [[NextMethod]]: undefined, [[Done]]: false }. + var iterated = { + "[[Iterator]]": obj, + "[[NextMethod]]": undefined, + "[[Done]]": false + }; + // 4. If chunkSize is not an integral Number, then + if (!IsInteger(chunkSize)) { + // a. Let error be ThrowCompletion(a newly created TypeError object). + var error = ThrowCompletion(new TypeError("`chunkSize` is not an integer")); + // b. Return ? IteratorClose(iterated, error). + return IteratorClose(iterated, error); + } + // 5. If chunkSize is not in the inclusive interval from 1๐”ฝ to ๐”ฝ(2**32 - 1), then + if (chunkSize < 1 || chunkSize > Math.pow(2, 32) - 1) { + // a. Let error be ThrowCompletion(a newly created RangeError object). + error = ThrowCompletion(new RangeError("`chunkSize` is invalid")); + // b. Return ? IteratorClose(iterated, error). + return IteratorClose(iterated, error); + } + // 6. Set iterated to ? GetIteratorDirect(obj). + iterated = { + "[[Iterator]]": obj, + "[[NextMethod]]": Get(obj, "next"), + "[[Done]]": false + }; + // 7. Let closure be a new Abstract Closure with no parameters that captures iterated and chunkSize and performs the following steps when called: + // 8. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, ยซ [[UnderlyingIterators]] ยป). + var result = Object.create(IteratorHelperPrototype); + result["[[Done]]"] = false; + + // a. Let buffer be a new empty List. + var buffer = []; + + CreateMethodProperty(result, "next", function () { + if (this["[[Done]]"] === true) { + return CreateIterResultObject(undefined, true); + } + + // b. Repeat, + while (true) { + // i. Let value be ? IteratorStepValue(iterated). + try { + var value = IteratorStepValue(iterated); + } catch (_error) { + this["[[Done]]"] = true; + throw _error; + } + // ii. If value is DONE, then + if (value === IteratorStepValue.DONE) { + this["[[Done]]"] = true; + // 1. If buffer is not empty, then + if (buffer.length > 0) { + // a. Perform Completion(Yield(CreateArrayFromList(buffer))). + return CreateIterResultObject(buffer.slice(), true); + } + // 2. Return ReturnCompletion(undefined). + return CreateIterResultObject(undefined, true); + } + // iii. Append value to buffer. + buffer.push(value); + // iv. If the number of elements in buffer is โ„(chunkSize), then + if (buffer.length === chunkSize) { + // 1. Let completion be Completion(Yield(CreateArrayFromList(buffer))). + try { + var completion = CreateIterResultObject(buffer.slice(), false); + } + // 2. IfAbruptCloseIterator(completion, iterated). + catch (err) { + this["[[Done]]"] = true; + return IteratorClose(iterated, ThrowCompletion(err)); + } + // 3. Set buffer to a new empty List. + buffer = []; + return completion; + } + } + }); + + CreateMethodProperty(result, "return", function (value) { + if (this["[[Done]]"] !== true) { + IteratorClose(iterated, NormalCompletion(value)); + this["[[Done]]"] = true; + } + return CreateIterResultObject(undefined, true); + }); + + // 9. Set result.[[UnderlyingIterators]] to ยซ iterated ยป. + // 10. Return result. + return result; +}); diff --git a/polyfills/Iterator/prototype/chunks/polyfill.test.js b/polyfills/Iterator/prototype/chunks/polyfill.test.js new file mode 100644 index 000000000..5d7e5158b --- /dev/null +++ b/polyfills/Iterator/prototype/chunks/polyfill.test.js @@ -0,0 +1,75 @@ +/* global Iterator */ + +it("is a function", function () { + proclaim.isFunction(Iterator.prototype.chunks); +}); + +it("has correct arity", function () { + proclaim.arity(Iterator.prototype.chunks, 1); +}); + +it("has correct name", function () { + proclaim.hasName(Iterator.prototype.chunks, "chunks"); +}); + +it("is not enumerable", function () { + proclaim.isNotEnumerable(Iterator.prototype, "chunks"); +}); + +describe("chunks", function () { + function TestIterator(arr) { + var i = -1; + this.next = function () { + i++; + if (arr[i] === "error") { + throw new Error("uh oh"); + } + return { value: arr[i], done: i >= arr.length }; + }; + } + TestIterator.prototype = Iterator.prototype; + + it("should chunk an iterator", function () { + var iter = new TestIterator([1, 2, 3]).chunks(2); + proclaim.deepStrictEqual(iter.next().value, [1, 2]); + proclaim.deepStrictEqual(iter.next().value, [3]); + proclaim.equal(iter.next().value, undefined); + + iter = new TestIterator([1, 2, 3, 4]).chunks(2); + proclaim.deepStrictEqual(iter.next().value, [1, 2]); + proclaim.deepStrictEqual(iter.next().value, [3, 4]); + proclaim.equal(iter.next().value, undefined); + }); + + it("should throw for invalid `chunkSize`", function () { + proclaim.throws(function () { + new TestIterator([1, 2, 3]).chunks(NaN); + }, TypeError); + proclaim.throws(function () { + new TestIterator([1, 2, 3]).chunks(1.5); + }, TypeError); + proclaim.throws(function () { + new TestIterator([1, 2, 3]).chunks(0); + }, RangeError); + proclaim.throws(function () { + new TestIterator([1, 2, 3]).chunks(Math.pow(2, 32)); + }, RangeError); + }); + + it("should handle errors during iteration", function () { + var iter = new TestIterator([1, 2, "error", 4]).chunks(1); + proclaim.deepStrictEqual(iter.next().value, [1]); + proclaim.deepStrictEqual(iter.next().value, [2]); + proclaim.throws(function () { + iter.next(); + }, Error); + proclaim.equal(iter.next().value, undefined); + }); + + it("should return an object with the right prototype", function () { + var iter = new TestIterator([]).chunks(1); + // use `Iterator.prototype.take` as a way to get `IteratorHelperPrototype` + proclaim.isFunction(iter.take); + proclaim.equal(Object.getPrototypeOf(iter), Object.getPrototypeOf(iter.take(0))); + }); +}); diff --git a/polyfills/Iterator/prototype/includes/config.toml b/polyfills/Iterator/prototype/includes/config.toml new file mode 100644 index 000000000..71e2977cc --- /dev/null +++ b/polyfills/Iterator/prototype/includes/config.toml @@ -0,0 +1,32 @@ +aliases = [ "es2027" ] +dependencies = [ + "_ESAbstract.CreateMethodProperty", + "_ESAbstract.Get", + "_ESAbstract.IsInteger", + "_ESAbstract.IteratorClose", + "_ESAbstract.IteratorStepValue", + "_ESAbstract.NormalCompletion", + "_ESAbstract.SameValueZero", + "_ESAbstract.ThrowCompletion", + "_ESAbstract.Type", + "Iterator", + "Number.MAX_SAFE_INTEGER", +] +spec = "https://tc39.es/ecma262/#sec-iterator.prototype.includes" +docs = "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/includes" + +[browsers] +android = "*" +bb = "*" +chrome = "<154" +edge = "*" +edge_mob = "*" +firefox = "<154" +firefox_mob = "<154" +ie = "*" +ie_mob = "*" +opera = "<138" +op_mob = "<103" +safari = "*" +ios_saf = "*" +samsung_mob = "*" diff --git a/polyfills/Iterator/prototype/includes/detect.js b/polyfills/Iterator/prototype/includes/detect.js new file mode 100644 index 000000000..f5159075c --- /dev/null +++ b/polyfills/Iterator/prototype/includes/detect.js @@ -0,0 +1 @@ +"Iterator" in self && "includes" in self.Iterator.prototype; diff --git a/polyfills/Iterator/prototype/includes/polyfill.js b/polyfills/Iterator/prototype/includes/polyfill.js new file mode 100644 index 000000000..86db44094 --- /dev/null +++ b/polyfills/Iterator/prototype/includes/polyfill.js @@ -0,0 +1,75 @@ +/* global CreateMethodProperty, Get, IsInteger, Iterator, IteratorClose, IteratorStepValue, NormalCompletion, SameValueZero, ThrowCompletion, Type */ +// 27.1.3.3.9 Iterator.prototype.includes ( searchElement [ , skippedElements ] ) +CreateMethodProperty(Iterator.prototype, "includes", function includes(searchElement /* , skippedElements */) { + // 1. Let obj be the this value. + var obj = this; + // 2. If obj is not an Object, throw a TypeError exception. + if (Type(obj) !== "object") { + throw new TypeError("`this` is not an object"); + } + // 3. Let iterated be the Iterator Record { [[Iterator]]: obj, [[NextMethod]]: undefined, [[Done]]: false }. + var iterated = { + "[[Iterator]]": obj, + "[[NextMethod]]": undefined, + "[[Done]]": false + }; + // 4. If skippedElements is undefined, then + var skippedElements = arguments[1]; + if (skippedElements === undefined) { + // a. Let toSkip be +0๐”ฝ. + var toSkip = 0; + } + // 5. Else, + else { + // a. If skippedElements is not one of +โˆž๐”ฝ, -โˆž๐”ฝ, or an integral Number, then + if (skippedElements !== Infinity && skippedElements !== -Infinity && !IsInteger(skippedElements)) { + // i. Let error be ThrowCompletion(a newly created TypeError object). + var error = ThrowCompletion(new TypeError("`skippedElements` is not an integer")); + // ii. Return ? IteratorClose(iterated, error). + return IteratorClose(iterated, error); + } + // b. Let toSkip be skippedElements. + toSkip = skippedElements; + } + // 6. If toSkip < -0๐”ฝ, then + if (toSkip < 0) { + // a. Let error be ThrowCompletion(a newly created RangeError object). + error = ThrowCompletion(new RangeError("`skippedElements` is negative")); + // b. Return ? IteratorClose(iterated, error). + return IteratorClose(iterated, error); + } + // 7. If toSkip is finite and toSkip > ๐”ฝ(2**53 - 1), then + if (toSkip !== Infinity && toSkip > Number.MAX_SAFE_INTEGER) { + // a. Let error be ThrowCompletion(a newly created RangeError object). + error = ThrowCompletion(new RangeError("`skippedElements` is greater than MAX_SAFE_INTEGER")); + // b. Return ? IteratorClose(iterated, error). + return IteratorClose(iterated, error); + } + // 8. Let skipped be +0๐”ฝ. + var skipped = 0; + // 9. Set iterated to ? GetIteratorDirect(obj). + iterated = { + "[[Iterator]]": obj, + "[[NextMethod]]": Get(obj, "next"), + "[[Done]]": false + }; + // 10. Repeat, + while (true) { + // a. Let value be ? IteratorStepValue(iterated). + var value = IteratorStepValue(iterated); + // b. If value is DONE, return false. + if (value === IteratorStepValue.DONE) { + return false; + } + // c. If skipped < toSkip, then + if (skipped < toSkip) { + // i. Set skipped to skipped + 1๐”ฝ. + skipped++; + } + // d. Else if SameValueZero(value, searchElement) is true, then + else if (SameValueZero(value, searchElement) === true) { + // i. Return ? IteratorClose(iterated, NormalCompletion(true)). + return IteratorClose(iterated, NormalCompletion(true)); + } + } +}); diff --git a/polyfills/Iterator/prototype/includes/polyfill.test.js b/polyfills/Iterator/prototype/includes/polyfill.test.js new file mode 100644 index 000000000..f92077f10 --- /dev/null +++ b/polyfills/Iterator/prototype/includes/polyfill.test.js @@ -0,0 +1,54 @@ +/* global Iterator */ + +it("is a function", function () { + proclaim.isFunction(Iterator.prototype.includes); +}); + +it("has correct arity", function () { + proclaim.arity(Iterator.prototype.includes, 1); +}); + +it("has correct name", function () { + proclaim.hasName(Iterator.prototype.includes, "includes"); +}); + +it("is not enumerable", function () { + proclaim.isNotEnumerable(Iterator.prototype, "includes"); +}); + +describe("includes", function () { + function TestIterator(arr) { + var i = -1; + this.next = function () { + i++; + return { value: arr[i], done: i >= arr.length }; + }; + } + TestIterator.prototype = Iterator.prototype; + + it("should check whether an iterator includes an element", function () { + proclaim.isTrue(new TestIterator(["a", "b", "c"]).includes("a")); + proclaim.isTrue(new TestIterator(["a", "b", "c"]).includes("b", 1)); + proclaim.isFalse(new TestIterator(["a", "b", "c"]).includes("d")); + proclaim.isFalse(new TestIterator(["a", "b", "c"]).includes("b", 2)); + proclaim.isFalse(new TestIterator(["a", "b", "c"]).includes("b", Infinity)); + }); + + it("should throw for invalid `skippedElements`", function () { + proclaim.throws(function () { + new TestIterator(["a", "b", "c"]).includes("a", NaN); + }, TypeError); + proclaim.throws(function () { + new TestIterator(["a", "b", "c"]).includes("a", -1); + }, RangeError); + proclaim.throws(function () { + new TestIterator(["a", "b", "c"]).includes("a", -Infinity); + }, RangeError); + proclaim.throws(function () { + new TestIterator(["a", "b", "c"]).includes("a", .5); + }, TypeError); + proclaim.throws(function () { + new TestIterator(["a", "b", "c"]).includes("a", Number.MAX_SAFE_INTEGER + 1); + }, RangeError); + }); +}); diff --git a/polyfills/Iterator/prototype/join/config.toml b/polyfills/Iterator/prototype/join/config.toml new file mode 100644 index 000000000..56f18101a --- /dev/null +++ b/polyfills/Iterator/prototype/join/config.toml @@ -0,0 +1,29 @@ +aliases = [ "es2027" ] +dependencies = [ + "_ESAbstract.CreateMethodProperty", + "_ESAbstract.Get", + "_ESAbstract.IteratorClose", + "_ESAbstract.IteratorStepValue", + "_ESAbstract.ThrowCompletion", + "_ESAbstract.ToString", + "_ESAbstract.Type", + "Iterator", +] +spec = "https://tc39.es/ecma262/#sec-iterator.prototype.join" +docs = "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/join" + +[browsers] +android = "*" +bb = "*" +chrome = "<153" +edge = "*" +edge_mob = "*" +firefox = "<154" +firefox_mob = "<154" +ie = "*" +ie_mob = "*" +opera = "<137" +op_mob = "<103" +safari = "*" +ios_saf = "*" +samsung_mob = "*" diff --git a/polyfills/Iterator/prototype/join/detect.js b/polyfills/Iterator/prototype/join/detect.js new file mode 100644 index 000000000..51b075f68 --- /dev/null +++ b/polyfills/Iterator/prototype/join/detect.js @@ -0,0 +1 @@ +"Iterator" in self && "join" in self.Iterator.prototype; diff --git a/polyfills/Iterator/prototype/join/polyfill.js b/polyfills/Iterator/prototype/join/polyfill.js new file mode 100644 index 000000000..d4caf1fb9 --- /dev/null +++ b/polyfills/Iterator/prototype/join/polyfill.js @@ -0,0 +1,74 @@ +/* global CreateMethodProperty, Get, Iterator, IteratorClose, IteratorStepValue, ThrowCompletion, ToString, Type */ +// 27.1.3.3.10 Iterator.prototype.join ( separator ) +CreateMethodProperty(Iterator.prototype, "join", function join(separator) { + // 1. Let obj be the this value. + var obj = this; + // 2. If obj is not an Object, throw a TypeError exception. + if (Type(obj) !== "object") { + throw new TypeError("`this` is not an object"); + } + // 3. Let iterated be the Iterator Record { [[Iterator]]: obj, [[NextMethod]]: undefined, [[Done]]: false }. + var iterated = { + "[[Iterator]]": obj, + "[[NextMethod]]": undefined, + "[[Done]]": false + }; + // 4. If separator is undefined, then + if (separator === undefined) { + // a. Let separatorString be ",". + var separatorString = ","; + } + // 5. Else, + else { + // a. Let separatorString be Completion(ToString(separator)). + try { + separatorString = ToString(separator); + } + // b. IfAbruptCloseIterator(separatorString, iterated). + catch (error) { + return IteratorClose(iterated, ThrowCompletion(error)); + } + } + // 6. Set iterated to ? GetIteratorDirect(obj). + iterated = { + "[[Iterator]]": obj, + "[[NextMethod]]": Get(obj, "next"), + "[[Done]]": false + }; + // 7. Let result be the empty String. + var result = ""; + // 8. Let first be true. + var first = true; + // 9. Repeat, + while (true) { + // a. Let value be ? IteratorStepValue(iterated). + var value = IteratorStepValue(iterated); + // b. If value is DONE, return result. + if (value === IteratorStepValue.DONE) { + return result; + } + // c. If first is true, then + if (first === true) { + // i. Set first to false. + first = false; + } + // d. Else, + else { + // i. Set result to the string-concatenation of result and separatorString. + result += separatorString; + } + // e. If value is neither undefined nor null, then + if (value !== undefined && value !== null) { + // i. Let valueString be Completion(ToString(value)). + try { + var valueString = ToString(value); + } + // ii. IfAbruptCloseIterator(valueString, iterated). + catch (error) { + return IteratorClose(iterated, ThrowCompletion(error)); + } + // iii. Set result to the string-concatenation of result and valueString. + result += valueString; + } + } +}); diff --git a/polyfills/Iterator/prototype/join/polyfill.test.js b/polyfills/Iterator/prototype/join/polyfill.test.js new file mode 100644 index 000000000..cc1791b8b --- /dev/null +++ b/polyfills/Iterator/prototype/join/polyfill.test.js @@ -0,0 +1,49 @@ +/* global Iterator */ + +it("is a function", function () { + proclaim.isFunction(Iterator.prototype.join); +}); + +it("has correct arity", function () { + proclaim.arity(Iterator.prototype.join, 1); +}); + +it("has correct name", function () { + proclaim.hasName(Iterator.prototype.join, "join"); +}); + +it("is not enumerable", function () { + proclaim.isNotEnumerable(Iterator.prototype, "join"); +}); + +describe("join", function () { + function TestIterator(arr) { + var i = -1; + this.next = function () { + i++; + return { value: arr[i], done: i >= arr.length }; + }; + } + TestIterator.prototype = Iterator.prototype; + + it("should join elements of an iterator", function () { + proclaim.equal(new TestIterator(["a", "b", "c"]).join(), "a,b,c"); + proclaim.equal(new TestIterator(["a", "b", "c"]).join("."), "a.b.c"); + }); + + if ("Symbol" in self) { + var sym = self.Symbol("test"); + + it("should throw for a symbol element", function () { + proclaim.throws(function () { + new TestIterator([sym]).join(); + }, TypeError); + }); + + it("should throw for a symbol separator", function () { + proclaim.throws(function () { + new TestIterator([]).join(sym); + }, TypeError); + }); + } +}); diff --git a/polyfills/Iterator/prototype/windows/config.toml b/polyfills/Iterator/prototype/windows/config.toml new file mode 100644 index 000000000..f26654faf --- /dev/null +++ b/polyfills/Iterator/prototype/windows/config.toml @@ -0,0 +1,33 @@ +aliases = [ "es2027" ] +dependencies = [ + "_ESAbstract.CreateIterResultObject", + "_ESAbstract.CreateMethodProperty", + "_ESAbstract.Get", + "_ESAbstract.IsInteger", + "_ESAbstract.IteratorClose", + "_ESAbstract.IteratorStepValue", + "_ESAbstract.NormalCompletion", + "_ESAbstract.ThrowCompletion", + "_ESAbstract.Type", + "Iterator", + "Iterator.prototype.take", + "Symbol.iterator", +] +spec = "https://tc39.es/ecma262/#sec-iterator.prototype.windows" +docs = "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/windows" + +[browsers] +android = "*" +bb = "*" +chrome = "*" +edge = "*" +edge_mob = "*" +firefox = "<154" +firefox_mob = "<154" +ie = "*" +ie_mob = "*" +opera = "*" +op_mob = "*" +safari = "*" +ios_saf = "*" +samsung_mob = "*" diff --git a/polyfills/Iterator/prototype/windows/detect.js b/polyfills/Iterator/prototype/windows/detect.js new file mode 100644 index 000000000..f3c0cd88b --- /dev/null +++ b/polyfills/Iterator/prototype/windows/detect.js @@ -0,0 +1 @@ +"Iterator" in self && "windows" in self.Iterator.prototype; diff --git a/polyfills/Iterator/prototype/windows/polyfill.js b/polyfills/Iterator/prototype/windows/polyfill.js new file mode 100644 index 000000000..4dd626c3d --- /dev/null +++ b/polyfills/Iterator/prototype/windows/polyfill.js @@ -0,0 +1,130 @@ +/* global CreateIterResultObject, CreateMethodProperty, Get, IsInteger, Iterator, IteratorClose, IteratorStepValue, NormalCompletion, Symbol, ThrowCompletion, Type */ + +var IteratorHelperPrototype = (function () { + var iterator = Object.create(Iterator.prototype); + iterator[Symbol.iterator] = function () { + return { + next: function () { + return { value: undefined, done: true }; + } + }; + }; + // use `Iterator.prototype.take` as a way to get `IteratorHelperPrototype` + var iteratorHelper = iterator.take(0); + return Object.getPrototypeOf(iteratorHelper); +})(); + +// 27.1.3.3.16 Iterator.prototype.windows ( windowSize [ , undersized ] ) +CreateMethodProperty(Iterator.prototype, "windows", function windows(windowSize /* , undersized */) { + // 1. Let obj be the this value. + var obj = this; + // 2. If obj is not an Object, throw a TypeError exception. + if (Type(obj) !== "object") { + throw new TypeError("`this` is not an object"); + } + // 3. Let iterated be the Iterator Record { [[Iterator]]: obj, [[NextMethod]]: undefined, [[Done]]: false }. + var iterated = { + "[[Iterator]]": obj, + "[[NextMethod]]": undefined, + "[[Done]]": false + }; + // 4. If windowSize is not an integral Number, then + if (!IsInteger(windowSize)) { + // a. Let error be ThrowCompletion(a newly created TypeError object). + var error = ThrowCompletion(new TypeError("`windowSize` is not an integer")); + // b. Return ? IteratorClose(iterated, error). + return IteratorClose(iterated, error); + } + // 5. If windowSize is not in the inclusive interval from 1๐”ฝ to ๐”ฝ(2**32 - 1), then + if (windowSize < 1 || windowSize > Math.pow(2, 32) - 1) { + // a. Let error be ThrowCompletion(a newly created RangeError object). + error = ThrowCompletion(new RangeError("`windowSize` is invalid")); + // b. Return ? IteratorClose(iterated, error). + return IteratorClose(iterated, error); + } + // 6. If undersized is undefined, set undersized to "only-full". + var undersized = arguments[1]; + if (undersized === undefined) { + undersized = "only-full"; + } + // 7. If undersized is neither "only-full" nor "allow-partial", then + if (undersized !== "only-full" && undersized !== "allow-partial") { + // a. Let error be ThrowCompletion(a newly created TypeError object). + error = ThrowCompletion(new TypeError("`undersized` is invalid")) + // b. Return ? IteratorClose(iterated, error). + return IteratorClose(iterated, error); + } + + // 8. Set iterated to ? GetIteratorDirect(obj). + iterated = { + "[[Iterator]]": obj, + "[[NextMethod]]": Get(obj, "next"), + "[[Done]]": false + }; + // 9. Let closure be a new Abstract Closure with no parameters that captures iterated, windowSize, and undersized and performs the following steps when called: + // 10. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, ยซ [[UnderlyingIterators]] ยป). + var result = Object.create(IteratorHelperPrototype); + result["[[Done]]"] = false; + + // a. Let buffer be a new empty List. + var buffer = []; + + CreateMethodProperty(result, "next", function () { + if (this["[[Done]]"] === true) { + return CreateIterResultObject(undefined, true); + } + + // b. Repeat, + while (true) { + // i. Let value be ? IteratorStepValue(iterated). + try { + var value = IteratorStepValue(iterated); + } catch (_error) { + this["[[Done]]"] = true; + throw _error; + } + // ii. If value is DONE, then + if (value === IteratorStepValue.DONE) { + this["[[Done]]"] = true; + // 1. If undersized is "allow-partial", buffer is not empty, and the number of elements in buffer < โ„(windowSize), then + if (undersized === "allow-partial" && buffer.length > 0 && buffer.length < windowSize) { + // a. Perform Completion(Yield(CreateArrayFromList(buffer))). + return CreateIterResultObject(buffer.slice(), true); + } + // 2. Return ReturnCompletion(undefined). + return CreateIterResultObject(undefined, true); + } + // iii. If the number of elements in buffer is โ„(windowSize), then + if (buffer.length === windowSize) { + // 1. Remove the first element from buffer. + buffer.shift(); + } + // iv. Append value to buffer. + buffer.push(value); + // v. If the number of elements in buffer is โ„(windowSize), then + if (buffer.length === windowSize) { + // 1. Let completion be Completion(Yield(CreateArrayFromList(buffer))). + try { + return CreateIterResultObject(buffer.slice(), false); + } + // 2. IfAbruptCloseIterator(completion, iterated). + catch (err) { + this["[[Done]]"] = true; + return IteratorClose(iterated, ThrowCompletion(err)); + } + } + } + }); + + CreateMethodProperty(result, "return", function (value) { + if (this["[[Done]]"] !== true) { + IteratorClose(iterated, NormalCompletion(value)); + this["[[Done]]"] = true; + } + return CreateIterResultObject(undefined, true); + }); + + // 11. Set result.[[UnderlyingIterators]] to ยซ iterated ยป. + // 12. Return result. + return result; +}); diff --git a/polyfills/Iterator/prototype/windows/polyfill.test.js b/polyfills/Iterator/prototype/windows/polyfill.test.js new file mode 100644 index 000000000..2bf6d0d48 --- /dev/null +++ b/polyfills/Iterator/prototype/windows/polyfill.test.js @@ -0,0 +1,83 @@ +/* global Iterator */ + +it("is a function", function () { + proclaim.isFunction(Iterator.prototype.windows); +}); + +it("has correct arity", function () { + proclaim.arity(Iterator.prototype.windows, 1); +}); + +it("has correct name", function () { + proclaim.hasName(Iterator.prototype.windows, "windows"); +}); + +it("is not enumerable", function () { + proclaim.isNotEnumerable(Iterator.prototype, "windows"); +}); + +describe("windows", function () { + function TestIterator(arr) { + var i = -1; + this.next = function () { + i++; + if (arr[i] === "error") { + throw new Error("uh oh"); + } + return { value: arr[i], done: i >= arr.length }; + }; + } + TestIterator.prototype = Iterator.prototype; + + it("should window an iterator", function () { + var iter = new TestIterator([1, 2, 3]).windows(2); + proclaim.deepStrictEqual(iter.next().value, [1, 2]); + proclaim.deepStrictEqual(iter.next().value, [2, 3]); + proclaim.equal(iter.next().value, undefined); + + iter = new TestIterator([1, 2, 3]).windows(4); + proclaim.equal(iter.next().value, undefined); + + iter = new TestIterator([1, 2, 3]).windows(4, "allow-partial"); + proclaim.deepStrictEqual(iter.next().value, [1, 2, 3]); + proclaim.equal(iter.next().value, undefined); + }); + + it("should throw for invalid `windowSize`", function () { + proclaim.throws(function () { + new TestIterator([1, 2, 3]).windows(NaN); + }, TypeError); + proclaim.throws(function () { + new TestIterator([1, 2, 3]).windows(1.5); + }, TypeError); + proclaim.throws(function () { + new TestIterator([1, 2, 3]).windows(0); + }, RangeError); + proclaim.throws(function () { + new TestIterator([1, 2, 3]).windows(Math.pow(2, 32)); + }, RangeError); + }); + + it("should throw for invalid `undersized`", function () { + proclaim.throws(function () { + new TestIterator([1, 2, 3]).windows(1, "oops"); + }, TypeError); + }); + + it("should handle errors during iteration", function () { + var iter = new TestIterator([1, 2, "error", 4]).windows(1); + proclaim.deepStrictEqual(iter.next().value, [1]); + proclaim.deepStrictEqual(iter.next().value, [2]); + proclaim.throws(function () { + iter.next(); + }, Error); + proclaim.equal(iter.next().value, undefined); + }); + + it("should return an object with the right prototype", function () { + var iter = new TestIterator([]).windows(1); + // use `Iterator.prototype.take` as a way to get `IteratorHelperPrototype` + proclaim.isFunction(iter.take); + proclaim.equal(Object.getPrototypeOf(iter), Object.getPrototypeOf(iter.take(0))); + }); +});