Repository: sindresorhus/ow
Pull request #214 (2021-06-27 08:57:42)

Avoid stack inference for set, map, and object validators
- For the set, map, and object validators that check for the content (`keysOfType`, `valuesOfType`, `ofType`, `deepValuesOfType`), avoid the unnecessary inference of the stack label and make the error message more accurate.
- Change signature of `ofType` to move `name` parameter forward


Patch:
diff --git a/source/predicates/map.ts b/source/predicates/map.ts
index b5d512c..0b4b1ab 100644
--- a/source/predicates/map.ts
+++ b/source/predicates/map.ts
@@ -108,7 +108,7 @@ export class MapPredicate<T1 = unknown, T2 = unknown> extends Predicate<Map<T1,
 	keysOfType(predicate: Predicate<T1>): this {
 		return this.addValidator({
 			message: (_, label, error) => `(${label}) ${error}`,
-			validator: map => ofType(map.keys(), predicate)
+			validator: map => ofType(map.keys(), 'keys', predicate)
 		});
 	}
 
@@ -120,7 +120,7 @@ export class MapPredicate<T1 = unknown, T2 = unknown> extends Predicate<Map<T1,
 	valuesOfType(predicate: Predicate<T2>): this {
 		return this.addValidator({
 			message: (_, label, error) => `(${label}) ${error}`,
-			validator: map => ofType(map.values(), predicate)
+			validator: map => ofType(map.values(), 'values', predicate)
 		});
 	}
 
diff --git a/source/predicates/object.ts b/source/predicates/object.ts
index 9feb506..118c089 100644
--- a/source/predicates/object.ts
+++ b/source/predicates/object.ts
@@ -57,7 +57,7 @@ export class ObjectPredicate<T extends object = object> extends Predicate<T> {
 	valuesOfType<T>(predicate: BasePredicate<T>): this {
 		return this.addValidator({
 			message: (_, label, error) => `(${label}) ${error}`,
-			validator: object => ofType(Object.values(object), predicate, 'values')
+			validator: object => ofType(Object.values(object), 'values', predicate)
 		});
 	}
 
diff --git a/source/predicates/set.ts b/source/predicates/set.ts
index b88c1f5..ecb47bb 100644
--- a/source/predicates/set.ts
+++ b/source/predicates/set.ts
@@ -81,7 +81,7 @@ export class SetPredicate<T = any> extends Predicate<Set<T>> {
 	ofType(predicate: Predicate<T>): this {
 		return this.addValidator({
 			message: (_, label, error) => `(${label}) ${error}`,
-			validator: set => ofType(set, predicate)
+			validator: set => ofType(set, 'values', predicate)
 		});
 	}
 
diff --git a/source/utils/of-type-deep.ts b/source/utils/of-type-deep.ts
index e99fb90..16f5e12 100644
--- a/source/utils/of-type-deep.ts
+++ b/source/utils/of-type-deep.ts
@@ -1,10 +1,10 @@
 import is from '@sindresorhus/is';
 import {Predicate} from '../predicates/predicate';
-import ow from '..';
+import test from '../test';
 
 const ofTypeDeep = (object: unknown, predicate: Predicate): boolean => {
 	if (!is.plainObject(object)) {
-		ow(object, predicate);
+		test(object, 'deep values', predicate, false);
 		return true;
 	}
 
diff --git a/source/utils/of-type.ts b/source/utils/of-type.ts
index acbfa53..e6b8969 100644
--- a/source/utils/of-type.ts
+++ b/source/utils/of-type.ts
@@ -1,24 +1,18 @@
-import ow from '..';
 import test from '../test';
 import {BasePredicate} from '../predicates/base-predicate';
 
-// TODO: After we migrate all usages of this function to specify the optional 'name' parameter, we can change the parameter to be required.
 /**
 Test all the values in the collection against a provided predicate.
 
 @hidden
 @param source Source collection to test.
+@param name The name to call the collection of values, such as `values` or `keys`.
 @param predicate Predicate to test every item in the source collection against.
-@param name The name to call the collection of values, such as `values` or `keys`. If it is `undefined`, it uses the call stack to infer the label.
 */
-export default <T>(source: IterableIterator<T> | Set<T> | T[], predicate: BasePredicate<T>, name?: string): boolean | string => {
+export default <T>(source: IterableIterator<T> | Set<T> | T[], name: string, predicate: BasePredicate<T>): boolean | string => {
 	try {
 		for (const item of source) {
-			if (name) {
-				test(item, name, predicate, false);
-			} else {
-				ow(item, predicate);
-			}
+			test(item, name, predicate, false);
 		}
 
 		return true;


Test patch:
diff --git a/test/map.ts b/test/map.ts
index d796783..b03a4dc 100644
--- a/test/map.ts
+++ b/test/map.ts
@@ -156,11 +156,11 @@ test('map.keysOfType', t => {
 
 	t.throws(() => {
 		ow(new Map([['unicorn', '🦄']]), ow.map.keysOfType(ow.number));
-	}, '(Map) Expected argument to be of type `number` but received type `string`');
+	}, '(Map) Expected keys to be of type `number` but received type `string`');
 
 	t.throws(() => {
 		ow(new Map([['unicorn', '🦄']]), 'foo', ow.map.keysOfType(ow.number));
-	}, '(Map `foo`) Expected argument to be of type `number` but received type `string`');
+	}, '(Map `foo`) Expected keys to be of type `number` but received type `string`');
 });
 
 test('map.valuesOfType', t => {
@@ -178,11 +178,11 @@ test('map.valuesOfType', t => {
 
 	t.throws(() => {
 		ow(new Map([['unicorn', '🦄']]), ow.map.valuesOfType(ow.number));
-	}, '(Map) Expected argument to be of type `number` but received type `string`');
+	}, '(Map) Expected values to be of type `number` but received type `string`');
 
 	t.throws(() => {
 		ow(new Map([['unicorn', '🦄']]), 'foo', ow.map.valuesOfType(ow.number));
-	}, '(Map `foo`) Expected argument to be of type `number` but received type `string`');
+	}, '(Map `foo`) Expected values to be of type `number` but received type `string`');
 });
 
 test('map.empty', t => {
diff --git a/test/object.ts b/test/object.ts
index 4ff68ce..9a365d5 100644
--- a/test/object.ts
+++ b/test/object.ts
@@ -111,15 +111,15 @@ test('object.valuesOfTypeDeep', t => {
 
 	t.throws(() => {
 		ow({unicorn: {key: '🦄', value: 1}}, ow.object.deepValuesOfType(ow.string));
-	}, '(object `ow.any(ow.string`) Expected argument to be of type `string` but received type `number`');
+	}, '(object `ow.any(ow.string`) Expected deep values to be of type `string` but received type `number`');
 
 	t.throws(() => {
 		ow({unicorn: {key: '🦄', value: 1}}, 'foo', ow.object.deepValuesOfType(ow.string));
-	}, '(object `foo`) Expected argument to be of type `string` but received type `number`');
+	}, '(object `foo`) Expected deep values to be of type `string` but received type `number`');
 
 	t.throws(() => {
 		ow({a: {b: {c: {d: 1}, e: '2'}, f: 3}}, ow.object.deepValuesOfType(ow.number));
-	}, '(object `ow.string`) Expected argument to be of type `number` but received type `string`');
+	}, '(object `ow.string`) Expected deep values to be of type `number` but received type `string`');
 });
 
 test('object.deepEqual', t => {
diff --git a/test/set.ts b/test/set.ts
index bb8d4cf..867da33 100644
--- a/test/set.ts
+++ b/test/set.ts
@@ -124,11 +124,11 @@ test('set.ofType', t => {
 
 	t.throws(() => {
 		ow(new Set(['unicorn']), ow.set.ofType(ow.number));
-	}, '(Set) Expected argument to be of type `number` but received type `string`');
+	}, '(Set) Expected values to be of type `number` but received type `string`');
 
 	t.throws(() => {
 		ow(new Set(['unicorn']), 'foo', ow.set.ofType(ow.number));
-	}, '(Set `foo`) Expected argument to be of type `number` but received type `string`');
+	}, '(Set `foo`) Expected values to be of type `number` but received type `string`');
 });
 
 test('set.empty', t => {