Repository: sindresorhus/ow
Pull request #213 (2021-06-05 14:48:50)

Avoid unnecessary inference of stack for label
- For `ow.isValid`, it's acutually not necessary to inference the stack for the label because the message won't actually be used.
- For the check of elements, e.g. `ow.object.valuesOfType`, `ow.array.ofType`, `ow.map.keysOfType`, `ow.map.valuesOfType`, `ow.set.ofType`, the inference of the label can also be avoided.
- The original reason why I want to avoid the inference of label is not because of performance. It was because `Error.prototype.stack` is not in the standard. And it isn't available in [Goja](https://github.com/dop251/goja). When `ow` tries to use `callsites` to inference the stack, it will cause an error `TypeError: Cannot read property 'slice' of undefined or null` because of [this line](https://github.com/sindresorhus/callsites/blob/6dc8cb32082c54c65c0e38672e00162c9362be22/index.js#L6). For most cases, users of `ow` can explicitly specify the label to avoid the inference such as `ow(value, 'value', ow.string)`. However, the cases mentioned above is out of the control of the users.
- After the change in this PR, the error message is changed for some cases. I'm not sure if it is acceptable. If anyone has a better idea for this problem, feel free to commit to my branch or close my PR and create your own. :)

hanshsieh: I rolled back some changes and focus on `ow.object.ofType` only. Please check how it looks now. :)



Patch:
diff --git a/source/index.ts b/source/index.ts
index 81845f3..cbd9bcf 100644
--- a/source/index.ts
+++ b/source/index.ts
@@ -9,7 +9,7 @@ import test from './test';
 /**
 @hidden
 */
-export type Main = <T>(value: T, label: string | Function, predicate: BasePredicate<T>) => void;
+export type Main = <T>(value: T, label: string | Function, predicate: BasePredicate<T>, idLabel?: boolean) => void;
 
 // Extends is only necessary for the generated documentation to be cleaner. The loaders below infer the correct type.
 export interface Ow extends Modifiers, Predicates {
diff --git a/source/predicates/any.ts b/source/predicates/any.ts
index 22556a4..ae62b8c 100644
--- a/source/predicates/any.ts
+++ b/source/predicates/any.ts
@@ -13,12 +13,12 @@ export class AnyPredicate<T = unknown> implements BasePredicate<T> {
 		private readonly options: PredicateOptions = {}
 	) {}
 
-	[testSymbol](value: T, main: Main, label: string | Function): asserts value {
+	[testSymbol](value: T, main: Main, label: string | Function, idLabel: boolean): asserts value {
 		const errors = new Map<string, Set<string>>();
 
 		for (const predicate of this.predicates) {
 			try {
-				main(value, label, predicate);
+				main(value, label, predicate, idLabel);
 				return;
 			} catch (error: unknown) {
 				if (value === undefined && this.options.optional === true) {
diff --git a/source/predicates/base-predicate.ts b/source/predicates/base-predicate.ts
index e9c584a..c722a75 100644
--- a/source/predicates/base-predicate.ts
+++ b/source/predicates/base-predicate.ts
@@ -14,5 +14,5 @@ export const isPredicate = (value: unknown): value is BasePredicate => Boolean((
 @hidden
 */
 export interface BasePredicate<T = unknown> {
-	[testSymbol](value: T, main: Main, label: string | Function): void;
+	[testSymbol](value: T, main: Main, label: string | Function, idLabel?: boolean): void;
 }
diff --git a/source/predicates/object.ts b/source/predicates/object.ts
index c6d348b..9feb506 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)
+			validator: object => ofType(Object.values(object), predicate, 'values')
 		});
 	}
 
diff --git a/source/predicates/predicate.ts b/source/predicates/predicate.ts
index 81778c8..e1e949f 100644
--- a/source/predicates/predicate.ts
+++ b/source/predicates/predicate.ts
@@ -96,7 +96,7 @@ export class Predicate<T = unknown> implements BasePredicate<T> {
 	/**
 	@hidden
 	*/
-	[testSymbol](value: T, main: Main, label: string | Function): asserts value is T {
+	[testSymbol](value: T, main: Main, label: string | Function, idLabel: boolean): asserts value is T {
 		// Create a map of labels -> received errors.
 		const errors = new Map<string, Set<string>>();
 
@@ -119,9 +119,10 @@ export class Predicate<T = unknown> implements BasePredicate<T> {
 			}
 
 			const label2 = is.function_(label) ? label() : label;
+			const labelWithTick = (label2 && idLabel) ? `\`${label2}\`` : label2;
 
-			const label_ = label2 ?
-				`${this.type} \`${label2}\`` :
+			const label_ = labelWithTick ?
+				`${this.type} ${labelWithTick}` :
 				this.type;
 
 			const mapKey = label2 || this.type;
diff --git a/source/utils/of-type.ts b/source/utils/of-type.ts
index 2ebaf48..acbfa53 100644
--- a/source/utils/of-type.ts
+++ b/source/utils/of-type.ts
@@ -1,17 +1,24 @@
 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 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>): boolean | string => {
+export default <T>(source: IterableIterator<T> | Set<T> | T[], predicate: BasePredicate<T>, name?: string): boolean | string => {
 	try {
 		for (const item of source) {
-			ow(item, predicate);
+			if (name) {
+				test(item, name, predicate, false);
+			} else {
+				ow(item, predicate);
+			}
 		}
 
 		return true;


Test patch:
diff --git a/source/test.ts b/source/test.ts
index 280a8c0..8dea8a3 100644
--- a/source/test.ts
+++ b/source/test.ts
@@ -8,7 +8,8 @@ Validate the value against the provided predicate.
 @param value - Value to test.
 @param label - Label which should be used in error messages.
 @param predicate - Predicate to test to value against.
+@param idLabel - If true, the label is a variable or type. Default: true.
 */
-export default function test<T>(value: T, label: string | Function, predicate: BasePredicate<T>): void {
-	predicate[testSymbol](value, test, label);
+export default function test<T>(value: T, label: string | Function, predicate: BasePredicate<T>, idLabel = true): void {
+	predicate[testSymbol](value, test, label, idLabel);
 }
diff --git a/test/object.ts b/test/object.ts
index baf46f5..4ff68ce 100644
--- a/test/object.ts
+++ b/test/object.ts
@@ -77,19 +77,19 @@ test('object.valuesOfType', t => {
 
 	t.throws(() => {
 		ow({unicorn: '🦄', rainbow: 2}, ow.object.valuesOfType(ow.string));
-	}, '(object) Expected argument to be of type `string` but received type `number`');
+	}, '(object) Expected values to be of type `string` but received type `number`');
 
 	t.throws(() => {
 		ow({unicorn: '🦄', rainbow: 2}, 'foo', ow.object.valuesOfType(ow.string));
-	}, '(object `foo`) Expected argument to be of type `string` but received type `number`');
+	}, '(object `foo`) Expected values to be of type `string` but received type `number`');
 
 	t.throws(() => {
 		ow({unicorn: 'a', rainbow: 'b'}, ow.object.valuesOfType(ow.string.minLength(2)));
-	}, '(object `ow.number`) Expected string to have a minimum length of `2`, got `a`');
+	}, '(object `ow.number`) Expected string values to have a minimum length of `2`, got `a`');
 
 	t.throws(() => {
 		ow(['🦄', true, 1], ow.object.valuesOfType(ow.any(ow.string, ow.boolean)));
-	}, '(object) Any predicate failed with the following errors:\n  - Expected argument to be of type `string` but received type `number`\n  - Expected argument to be of type `boolean` but received type `number`');
+	}, '(object) Any predicate failed with the following errors:\n  - Expected values to be of type `string` but received type `number`\n  - Expected values to be of type `boolean` but received type `number`');
 });
 
 test('object.valuesOfTypeDeep', t => {