Skip to content

Commit 74be6fe

Browse files
errors: extract type detection & use in ERR_INVALID_RETURN_VALUE
1 parent 3507b3f commit 74be6fe

File tree

3 files changed

+195
-27
lines changed

3 files changed

+195
-27
lines changed

lib/internal/errors.js

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,44 @@ const genericNodeError = hideStackFrames(function genericNodeError(message, erro
858858
return err;
859859
});
860860

861+
/**
862+
* Determine the specific type of a value for type-mismatch errors.
863+
* @param {*} value
864+
* @returns {string}
865+
*/
866+
function determineSpecificType(value) {
867+
let type = '';
868+
869+
if (value == null) {
870+
type += value;
871+
} else if (typeof value === 'function' && value.name) {
872+
type = `function ${value.name}`;
873+
} else if (typeof value === 'object') {
874+
if (value.constructor?.name) {
875+
type = `an instance of ${value.constructor.name}`;
876+
} else {
877+
const inspected = lazyInternalUtilInspect()
878+
.inspect(value, { depth: -1 });
879+
880+
if (StringPrototypeIncludes(inspected, '[Object: null prototype]')) {
881+
type = 'an instance of Object';
882+
} else {
883+
type = inspected;
884+
}
885+
}
886+
} else {
887+
let inspected = lazyInternalUtilInspect()
888+
.inspect(value, { colors: false });
889+
if (inspected.length > 25) {
890+
inspected = `${StringPrototypeSlice(inspected, 0, 25)}...`;
891+
}
892+
893+
type = `type ${typeof value} (${inspected})`;
894+
}
895+
896+
return type;
897+
}
898+
861899
module.exports = {
862900
AbortError,
863901
aggregateTwoErrors,
@@ -866,6 +904,7 @@ module.exports = {
866904
connResetException,
867905
dnsException,
868906
// This is exported only to facilitate testing.
907+
determineSpecificType,
869908
E,
870909
errnoException,
871910
exceptionWithHostPort,
@@ -1237,25 +1276,8 @@ E('ERR_INVALID_ARG_TYPE',
12371276
}
12381277
}
12391278

1240-
if (actual == null) {
1241-
msg += `. Received ${actual}`;
1242-
} else if (typeof actual === 'function' && actual.name) {
1243-
msg += `. Received function ${actual.name}`;
1244-
} else if (typeof actual === 'object') {
1245-
if (actual.constructor?.name) {
1246-
msg += `. Received an instance of ${actual.constructor.name}`;
1247-
} else {
1248-
const inspected = lazyInternalUtilInspect()
1249-
.inspect(actual, { depth: -1 });
1250-
msg += `. Received ${inspected}`;
1251-
}
1252-
} else {
1253-
let inspected = lazyInternalUtilInspect()
1254-
.inspect(actual, { colors: false });
1255-
if (inspected.length > 25)
1256-
inspected = `${StringPrototypeSlice(inspected, 0, 25)}...`;
1257-
msg += `. Received type ${typeof actual} (${inspected})`;
1258-
}
1279+
msg += `. Received ${determineSpecificType(actual)}`;
1280+
12591281
return msg;
12601282
}, TypeError);
12611283
E('ERR_INVALID_ARG_VALUE', (name, value, reason = 'is invalid') => {
@@ -1335,12 +1357,8 @@ E('ERR_INVALID_RETURN_PROPERTY_VALUE', (input, name, prop, value) => {
13351357
` "${name}" function but got ${type}.`;
13361358
}, TypeError);
13371359
E('ERR_INVALID_RETURN_VALUE', (input, name, value) => {
1338-
let type;
1339-
if (value?.constructor?.name) {
1340-
type = `instance of ${value.constructor.name}`;
1341-
} else {
1342-
type = `type ${typeof value}`;
1343-
}
1360+
const type = determineSpecificType(value);
1361+
13441362
return `Expected ${input} to be returned from the "${name}"` +
13451363
` function but got ${type}.`;
13461364
}, TypeError, RangeError);
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// Flags: --expose-internals
2+
3+
import '../common/index.mjs';
4+
import { strictEqual } from 'node:assert';
5+
import errorsModule from 'internal/errors';
6+
7+
8+
const { determineSpecificType } = errorsModule;
9+
10+
strictEqual(
11+
determineSpecificType(1n),
12+
'type bigint (1n)',
13+
);
14+
15+
strictEqual(
16+
determineSpecificType(false),
17+
'type boolean (false)',
18+
);
19+
20+
strictEqual(
21+
determineSpecificType(2),
22+
'type number (2)',
23+
);
24+
25+
strictEqual(
26+
determineSpecificType(NaN),
27+
'type number (NaN)',
28+
);
29+
30+
strictEqual(
31+
determineSpecificType(Infinity),
32+
'type number (Infinity)',
33+
);
34+
35+
strictEqual(
36+
determineSpecificType(''),
37+
"type string ('')",
38+
);
39+
40+
strictEqual(
41+
determineSpecificType(Symbol('foo')),
42+
'type symbol (Symbol(foo))',
43+
);
44+
45+
strictEqual(
46+
determineSpecificType(function foo() {}),
47+
'function foo',
48+
);
49+
50+
strictEqual(
51+
determineSpecificType(null),
52+
'null',
53+
);
54+
55+
strictEqual(
56+
determineSpecificType(undefined),
57+
'undefined',
58+
);
59+
60+
strictEqual(
61+
determineSpecificType([]),
62+
'an instance of Array',
63+
);
64+
65+
strictEqual(
66+
determineSpecificType(new Array(0)),
67+
'an instance of Array',
68+
);
69+
strictEqual(
70+
determineSpecificType(new BigInt64Array(0)),
71+
'an instance of BigInt64Array',
72+
);
73+
strictEqual(
74+
determineSpecificType(new BigUint64Array(0)),
75+
'an instance of BigUint64Array',
76+
);
77+
strictEqual(
78+
determineSpecificType(new Int8Array(0)),
79+
'an instance of Int8Array',
80+
);
81+
strictEqual(
82+
determineSpecificType(new Int16Array(0)),
83+
'an instance of Int16Array',
84+
);
85+
strictEqual(
86+
determineSpecificType(new Int32Array(0)),
87+
'an instance of Int32Array',
88+
);
89+
strictEqual(
90+
determineSpecificType(new Float32Array(0)),
91+
'an instance of Float32Array',
92+
);
93+
strictEqual(
94+
determineSpecificType(new Float64Array(0)),
95+
'an instance of Float64Array',
96+
);
97+
strictEqual(
98+
determineSpecificType(new Uint8Array(0)),
99+
'an instance of Uint8Array',
100+
);
101+
strictEqual(
102+
determineSpecificType(new Uint8ClampedArray(0)),
103+
'an instance of Uint8ClampedArray',
104+
);
105+
strictEqual(
106+
determineSpecificType(new Uint16Array(0)),
107+
'an instance of Uint16Array',
108+
);
109+
strictEqual(
110+
determineSpecificType(new Uint32Array(0)),
111+
'an instance of Uint32Array',
112+
);
113+
114+
strictEqual(
115+
determineSpecificType(new Date()),
116+
'an instance of Date',
117+
);
118+
119+
strictEqual(
120+
determineSpecificType(new Map()),
121+
'an instance of Map',
122+
);
123+
strictEqual(
124+
determineSpecificType(new WeakMap()),
125+
'an instance of WeakMap',
126+
);
127+
128+
strictEqual(
129+
determineSpecificType(Object.create(null)),
130+
'an instance of Object',
131+
);
132+
strictEqual(
133+
determineSpecificType({}),
134+
'an instance of Object',
135+
);
136+
137+
strictEqual(
138+
determineSpecificType(Promise.resolve('foo')),
139+
'an instance of Promise',
140+
);
141+
142+
strictEqual(
143+
determineSpecificType(new Set()),
144+
'an instance of Set',
145+
);
146+
strictEqual(
147+
determineSpecificType(new WeakSet()),
148+
'an instance of WeakSet',
149+
);

test/parallel/test-assert-async.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,9 @@ const invalidThenableFunc = () => {
103103
promises.push(assert.rejects(promise, {
104104
name: 'TypeError',
105105
code: 'ERR_INVALID_RETURN_VALUE',
106+
// FIXME: This should use substring matching for key words, like /Promise/ and /undefined/
106107
message: 'Expected instance of Promise to be returned ' +
107-
'from the "promiseFn" function but got type undefined.'
108+
'from the "promiseFn" function but got undefined.'
108109
}));
109110

110111
promise = assert.rejects(Promise.resolve(), common.mustNotCall());
@@ -162,7 +163,7 @@ promises.push(assert.rejects(
162163
let promise = assert.doesNotReject(() => new Map(), common.mustNotCall());
163164
promises.push(assert.rejects(promise, {
164165
message: 'Expected instance of Promise to be returned ' +
165-
'from the "promiseFn" function but got instance of Map.',
166+
'from the "promiseFn" function but got an instance of Map.',
166167
code: 'ERR_INVALID_RETURN_VALUE',
167168
name: 'TypeError'
168169
}));

0 commit comments

Comments
 (0)