Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions lib/src/array/forEach.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Licensed under the MIT license.
*/

import { isStrictNullOrUndefined } from "../helpers/base";
import { CALL, LENGTH } from "../internal/constants";

/**
Expand Down Expand Up @@ -50,12 +51,14 @@ import { CALL, LENGTH } from "../internal/constants";
* const items = { length: 3, 0: 'item1', 1: 'item2', 2: 'item3' };
* ```
*/
export function arrForEach<T = any>(theArray: ArrayLike<T>, callbackfn: (value: T, index: number, array: T[]) => void | number, thisArg?: any): void {
export function arrForEach<T = any>(theArray: ArrayLike<T>, callbackfn: (value: T, index: number, array: T[]) => void | number, thisArg?: any): void;
export function arrForEach<T = any>(theArray: ArrayLike<T>, callbackfn: (value: T, index: number, array: ArrayLike<T>) => void | number, thisArg?: any): void;
export function arrForEach<T = any, A extends ArrayLike<T> = ArrayLike<T>>(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;
}
}
Expand Down
3 changes: 2 additions & 1 deletion lib/src/iterator/forOf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -68,7 +69,7 @@ export function iterForOf<T>(iter: Iterator<T> | Iterable<T>, 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) {
Comment thread
nev21 marked this conversation as resolved.
break;
}

Expand Down
4 changes: 2 additions & 2 deletions lib/src/object/for_each_key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -42,7 +42,7 @@ export function objForEachKey<T>(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) {
Comment thread
nev21 marked this conversation as resolved.
break;
}
}
Expand Down
40 changes: 40 additions & 0 deletions lib/test/src/common/helpers/array.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
44 changes: 44 additions & 0 deletions lib/test/src/common/iterator/forOf.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
});
});
42 changes: 37 additions & 5 deletions lib/test/src/common/object/for_each_key.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
Loading