diff --git a/lib/src/array/forEach.ts b/lib/src/array/forEach.ts index 95060a2d..c3a74b96 100644 --- a/lib/src/array/forEach.ts +++ b/lib/src/array/forEach.ts @@ -6,6 +6,7 @@ * Licensed under the MIT license. */ +import { isStrictNullOrUndefined } from "../helpers/base"; import { CALL, LENGTH } from "../internal/constants"; /** @@ -50,12 +51,14 @@ import { CALL, LENGTH } from "../internal/constants"; * const items = { length: 3, 0: 'item1', 1: 'item2', 2: 'item3' }; * ``` */ -export function arrForEach(theArray: ArrayLike, callbackfn: (value: T, index: number, array: T[]) => void | number, thisArg?: any): void { +export function arrForEach(theArray: ArrayLike, callbackfn: (value: T, index: number, array: T[]) => void | number, thisArg?: any): void; +export function arrForEach(theArray: ArrayLike, callbackfn: (value: T, index: number, array: ArrayLike) => void | number, thisArg?: any): void; +export function arrForEach = ArrayLike>(theArray: A, callbackfn: (value: T, index: number, array: A) => void | number, thisArg?: any): void { if (theArray) { const len = theArray[LENGTH] >>> 0; for (let idx = 0; idx < len; idx++) { if (idx in theArray) { - if (callbackfn[CALL](thisArg || theArray, theArray[idx], idx, theArray) === -1) { + if (callbackfn[CALL](isStrictNullOrUndefined(thisArg) ? theArray as any : thisArg, theArray[idx], idx, theArray) === -1) { break; } } diff --git a/lib/src/iterator/forOf.ts b/lib/src/iterator/forOf.ts index 45e62ca1..eeb34954 100644 --- a/lib/src/iterator/forOf.ts +++ b/lib/src/iterator/forOf.ts @@ -7,6 +7,7 @@ */ import { ICachedValue, createCachedValue } from "../helpers/cache"; +import { isStrictNullOrUndefined } from "../helpers/base"; import { CALL, NULL_VALUE, UNDEF_VALUE } from "../internal/constants"; import { getKnownSymbol } from "../symbol/symbol"; import { WellKnownSymbols } from "../symbol/well_known"; @@ -68,7 +69,7 @@ export function iterForOf(iter: Iterator | Iterable, callbackfn: (value try { let count = 0; while(!(iterResult = iter.next()).done) { - if (callbackfn[CALL](thisArg || iter, iterResult.value, count, iter) === -1) { + if (callbackfn[CALL](isStrictNullOrUndefined(thisArg) ? iter : thisArg, iterResult.value, count, iter) === -1) { break; } diff --git a/lib/src/object/for_each_key.ts b/lib/src/object/for_each_key.ts index 5fa17e78..19e86281 100644 --- a/lib/src/object/for_each_key.ts +++ b/lib/src/object/for_each_key.ts @@ -6,7 +6,7 @@ * Licensed under the MIT license. */ -import { isFunction, isObject } from "../helpers/base"; +import { isFunction, isObject, isStrictNullOrUndefined } from "../helpers/base"; import { CALL } from "../internal/constants"; import { objHasOwn } from "./has_own"; @@ -42,7 +42,7 @@ export function objForEachKey(theObject: T, callbackfn: (key: string, value: if (theObject && (isObject(theObject) || isFunction(theObject))) { for (const prop in theObject) { if (objHasOwn(theObject, prop)) { - if (callbackfn[CALL](thisArg || theObject, prop, theObject[prop as keyof T]) === -1) { + if (callbackfn[CALL](isStrictNullOrUndefined(thisArg) ? theObject : thisArg, prop, theObject[prop as keyof T]) === -1) { break; } } diff --git a/lib/test/src/common/helpers/array.test.ts b/lib/test/src/common/helpers/array.test.ts index 85a8ecd4..1aa33d23 100644 --- a/lib/test/src/common/helpers/array.test.ts +++ b/lib/test/src/common/helpers/array.test.ts @@ -152,6 +152,46 @@ describe("array helpers", () => { } }); + + it("Validate falsy thisArg values are used correctly (not replaced by array)", () => { + const arr = [1, 2, 3]; + let capturedThis: any; + + + // omitted thisArg → falls back to the array + arrForEach(arr, function(this: any) { + capturedThis = this; + }); + assert.strictEqual(capturedThis, arr, "omitting thisArg should use the array as this"); + + // explicit undefined → same as omitted + arrForEach(arr, function(this: any) { + capturedThis = this; + }, undefined); + assert.strictEqual(capturedThis, arr, "explicit undefined thisArg should use the array as this"); + + // null → same as undefined, falls back to array + arrForEach(arr, function(this: any) { + capturedThis = this; + }, null); + assert.strictEqual(capturedThis, arr, "null thisArg should use the array as this"); + + // falsy-but-not-null/undefined values are used as-is + arrForEach(arr, function(this: any) { + capturedThis = this; + }, 0); + assert.strictEqual(capturedThis, 0, "thisArg of 0 should be used as-is"); + + arrForEach(arr, function(this: any) { + capturedThis = this; + }, ""); + assert.strictEqual(capturedThis, "", "thisArg of empty string should be used as-is"); + + arrForEach(arr, function(this: any) { + capturedThis = this; + }, false); + assert.strictEqual(capturedThis, false, "thisArg of false should be used as-is"); + }); }); describe("arrAppend", () => { diff --git a/lib/test/src/common/iterator/forOf.test.ts b/lib/test/src/common/iterator/forOf.test.ts index b23cb5f4..401cf66f 100644 --- a/lib/test/src/common/iterator/forOf.test.ts +++ b/lib/test/src/common/iterator/forOf.test.ts @@ -176,5 +176,49 @@ describe("create iterator helpers", () => { assert.equal(values[0], 10); assert.equal(values[1], 20); }); + + it("omitting or passing null/undefined thisArg falls back to the iterator", () => { + let capturedThis: any; + + // omitted thisArg → falls back to the iterator + const iter1 = createArrayIterator([1]); + iterForOf(iter1, function(this: any) { + capturedThis = this; + }); + assert.strictEqual(capturedThis, iter1, "omitting thisArg should use the iterator as this"); + + // explicit undefined → same as omitted + const iter2 = createArrayIterator([1]); + iterForOf(iter2, function(this: any) { + capturedThis = this; + }, undefined); + assert.strictEqual(capturedThis, iter2, "explicit undefined thisArg should use the iterator as this"); + + // null → same as undefined, falls back to iterator + const iter3 = createArrayIterator([1]); + iterForOf(iter3, function(this: any) { + capturedThis = this; + }, null); + assert.strictEqual(capturedThis, iter3, "null thisArg should use the iterator as this"); + }); + + it("falsy-but-not-null/undefined thisArg values are used as-is", () => { + let capturedThis: any; + + iterForOf(createArrayIterator([1]), function(this: any) { + capturedThis = this; + }, 0); + assert.strictEqual(capturedThis, 0, "thisArg of 0 should be used as-is"); + + iterForOf(createArrayIterator([1]), function(this: any) { + capturedThis = this; + }, ""); + assert.strictEqual(capturedThis, "", "thisArg of empty string should be used as-is"); + + iterForOf(createArrayIterator([1]), function(this: any) { + capturedThis = this; + }, false); + assert.strictEqual(capturedThis, false, "thisArg of false should be used as-is"); + }); }); }); \ No newline at end of file diff --git a/lib/test/src/common/object/for_each_key.test.ts b/lib/test/src/common/object/for_each_key.test.ts index 6e3789eb..f4c90bcf 100644 --- a/lib/test/src/common/object/for_each_key.test.ts +++ b/lib/test/src/common/object/for_each_key.test.ts @@ -37,15 +37,47 @@ describe("object for_each_key tests", () => { assert.strictEqual(actualThis, thisObj, "Should use the provided thisArg"); }); - it("should use the object as thisArg when not provided", () => { - const obj = { a: 1, b: 2 }; - let actualThis: any; + it("should use the object as this when thisArg is omitted, undefined, or null", () => { + const obj = { a: 1 }; + let capturedThis: any; + // omitted thisArg → falls back to the object objForEachKey(obj, function(this: any) { - actualThis = this; + capturedThis = this; }); + assert.strictEqual(capturedThis, obj, "omitting thisArg should use the object as this"); + + // explicit undefined → same as omitted + objForEachKey(obj, function(this: any) { + capturedThis = this; + }, undefined); + assert.strictEqual(capturedThis, obj, "explicit undefined thisArg should use the object as this"); + + // null → same as undefined, falls back to object + objForEachKey(obj, function(this: any) { + capturedThis = this; + }, null); + assert.strictEqual(capturedThis, obj, "null thisArg should use the object as this"); + }); + + it("should use falsy-but-not-null/undefined thisArg values as-is", () => { + const obj = { a: 1 }; + let capturedThis: any; + + objForEachKey(obj, function(this: any) { + capturedThis = this; + }, 0); + assert.strictEqual(capturedThis, 0, "thisArg of 0 should be used as-is"); - assert.strictEqual(actualThis, obj, "Should use the object as thisArg when not provided"); + objForEachKey(obj, function(this: any) { + capturedThis = this; + }, ""); + assert.strictEqual(capturedThis, "", "thisArg of empty string should be used as-is"); + + objForEachKey(obj, function(this: any) { + capturedThis = this; + }, false); + assert.strictEqual(capturedThis, false, "thisArg of false should be used as-is"); }); it("should break iteration when callback returns -1", () => {