Skip to content

Commit df94cfb

Browse files
BridgeARBethGriggs
authored andcommitted
errors: improve ERR_INVALID_ARG_TYPE
ERR_INVALID_ARG_TYPE is the most common error used throughout the code base. This improves the error message by providing more details to the user and by indicating more precisely which values are allowed ones and which ones are not. It adds the actual input to the error message in case it's a primitive. If it's a class instance, it'll print the class name instead of "object" and "falsy" or similar entries are not named "type" anymore. PR-URL: #29675 Reviewed-By: Rich Trott <[email protected]>
1 parent ae3459a commit df94cfb

File tree

127 files changed

+665
-537
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

127 files changed

+665
-537
lines changed

‎lib/internal/errors.js‎

Lines changed: 123 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,21 @@ const{
2626
constmessages=newMap();
2727
constcodes={};
2828

29+
constclassRegExp=/^([A-Z][a-z0-9]*)+$/;
30+
// Sorted by a rough estimate on most frequently used entries.
31+
constkTypes=[
32+
'string',
33+
'function',
34+
'number',
35+
'object',
36+
// Accept 'Function' and 'Object' as alternative to the lower cased version.
37+
'Function',
38+
'Object',
39+
'boolean',
40+
'bigint',
41+
'symbol'
42+
];
43+
2944
const{ kMaxLength }=internalBinding('buffer');
3045

3146
constMainContextError=Error;
@@ -616,26 +631,6 @@ function isStackOverflowError(err){
616631
err.message===maxStack_ErrorMessage;
617632
}
618633

619-
functiononeOf(expected,thing){
620-
assert(typeofthing==='string','`thing` has to be of type string');
621-
if(ArrayIsArray(expected)){
622-
constlen=expected.length;
623-
assert(len>0,
624-
'At least one expected value needs to be specified');
625-
expected=expected.map((i)=>String(i));
626-
if(len>2){
627-
return`one of ${thing}${expected.slice(0,len-1).join(', ')}, or `+
628-
expected[len-1];
629-
}elseif(len===2){
630-
return`one of ${thing}${expected[0]} or ${expected[1]}`;
631-
}else{
632-
return`of ${thing}${expected[0]}`;
633-
}
634-
}else{
635-
return`of ${thing}${String(expected)}`;
636-
}
637-
}
638-
639634
// Only use this for integers! Decimal numbers do not work with this function.
640635
functionaddNumericalSeparator(val){
641636
letres='';
@@ -934,27 +929,114 @@ E('ERR_INVALID_ADDRESS_FAMILY', function(addressType, host, port){
934929
E('ERR_INVALID_ARG_TYPE',
935930
(name,expected,actual)=>{
936931
assert(typeofname==='string',"'name' must be a string");
932+
if(!ArrayIsArray(expected)){
933+
expected=[expected];
934+
}
935+
936+
letmsg='The ';
937+
if(name.endsWith(' argument')){
938+
// For cases like 'first argument'
939+
msg+=`${name} `;
940+
}else{
941+
consttype=name.includes('.') ? 'property' : 'argument';
942+
msg+=`"${name}" ${type} `;
943+
}
937944

938945
// determiner: 'must be' or 'must not be'
939-
letdeterminer;
940946
if(typeofexpected==='string'&&expected.startsWith('not ')){
941-
determiner='must not be';
947+
msg+='must not be';
942948
expected=expected.replace(/^not/,'');
943949
}else{
944-
determiner='must be';
950+
msg+='must be';
945951
}
946952

947-
letmsg;
948-
if(name.endsWith(' argument')){
949-
// For cases like 'first argument'
950-
msg=`The ${name}${determiner}${oneOf(expected,'type')}`;
951-
}else{
952-
consttype=name.includes('.') ? 'property' : 'argument';
953-
msg=`The "${name}" ${type}${determiner}${oneOf(expected,'type')}`;
953+
consttypes=[];
954+
constinstances=[];
955+
constother=[];
956+
957+
for(constvalueofexpected){
958+
assert(typeofvalue==='string',
959+
'All expected entries have to be of type string');
960+
if(kTypes.includes(value)){
961+
types.push(value.toLowerCase());
962+
}elseif(classRegExp.test(value)){
963+
instances.push(value);
964+
}else{
965+
assert(value!=='object',
966+
'The value "object" should be written as "Object"');
967+
other.push(value);
968+
}
954969
}
955970

956-
// TODO(BridgeAR): Improve the output by showing `null` and similar.
957-
msg+=`. Received type ${typeofactual}`;
971+
// Special handle `object` in case other instances are allowed to outline
972+
// the differences between each other.
973+
if(instances.length>0){
974+
constpos=types.indexOf('object');
975+
if(pos!==-1){
976+
types.splice(pos,1);
977+
instances.push('Object');
978+
}
979+
}
980+
981+
if(types.length>0){
982+
if(types.length>2){
983+
constlast=types.pop();
984+
msg+=`one of type ${types.join(', ')}, or ${last}`;
985+
}elseif(types.length===2){
986+
msg+=`one of type ${types[0]} or ${types[1]}`;
987+
}else{
988+
msg+=`of type ${types[0]}`;
989+
}
990+
if(instances.length>0||other.length>0)
991+
msg+=' or ';
992+
}
993+
994+
if(instances.length>0){
995+
if(instances.length>2){
996+
constlast=instances.pop();
997+
msg+=`an instance of ${instances.join(', ')}, or ${last}`;
998+
}else{
999+
msg+=`an instance of ${instances[0]}`;
1000+
if(instances.length===2){
1001+
msg+=` or ${instances[1]}`;
1002+
}
1003+
}
1004+
if(other.length>0)
1005+
msg+=' or ';
1006+
}
1007+
1008+
if(other.length>0){
1009+
if(other.length>2){
1010+
constlast=other.pop();
1011+
msg+=`one of ${other.join(', ')}, or ${last}`;
1012+
}elseif(other.length===2){
1013+
msg+=`one of ${other[0]} or ${other[1]}`;
1014+
}else{
1015+
if(other[0].toLowerCase()!==other[0])
1016+
msg+='an ';
1017+
msg+=`${other[0]}`;
1018+
}
1019+
}
1020+
1021+
if(actual==null){
1022+
msg+=`. Received ${actual}`;
1023+
}elseif(typeofactual==='function'&&actual.name){
1024+
msg+=`. Received function ${actual.name}`;
1025+
}elseif(typeofactual==='object'){
1026+
if(actual.constructor&&actual.constructor.name){
1027+
msg+=`. Received an instance of ${actual.constructor.name}`;
1028+
}else{
1029+
constinspected=lazyInternalUtilInspect()
1030+
.inspect(actual,{depth: -1});
1031+
msg+=`. Received ${inspected}`;
1032+
}
1033+
}else{
1034+
letinspected=lazyInternalUtilInspect()
1035+
.inspect(actual,{colors: false});
1036+
if(inspected.length>25)
1037+
inspected=`${inspected.slice(0,25)}...`;
1038+
msg+=`. Received type ${typeofactual} (${inspected})`;
1039+
}
9581040
returnmsg;
9591041
},TypeError);
9601042
E('ERR_INVALID_ARG_VALUE',(name,value,reason='is invalid')=>{
@@ -1042,7 +1124,15 @@ E('ERR_INVALID_URL', function(input){
10421124
return`Invalid URL: ${input}`;
10431125
},TypeError);
10441126
E('ERR_INVALID_URL_SCHEME',
1045-
(expected)=>`The URL must be ${oneOf(expected,'scheme')}`,TypeError);
1127+
(expected)=>{
1128+
if(typeofexpected==='string')
1129+
expected=[expected];
1130+
assert(expected.length<=2);
1131+
constres=expected.length===2 ?
1132+
`one of scheme ${expected[0]} or ${expected[1]}` :
1133+
`of scheme ${expected[0]}`;
1134+
return`The URL must be ${res}`;
1135+
},TypeError);
10461136
E('ERR_IPC_CHANNEL_CLOSED','Channel closed',Error);
10471137
E('ERR_IPC_DISCONNECTED','IPC channel is already disconnected',Error);
10481138
E('ERR_IPC_ONE_PIPE','Child process can have only one IPC pipe',Error);

‎test/common/index.js‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,26 @@ function runWithInvalidFD(func){
718718
printSkipMessage('Could not generate an invalid fd');
719719
}
720720

721+
// A helper function to simplify checking for ERR_INVALID_ARG_TYPE output.
722+
functioninvalidArgTypeHelper(input){
723+
if(input==null){
724+
return` Received ${input}`;
725+
}
726+
if(typeofinput==='function'&&input.name){
727+
return` Received function ${input.name}`;
728+
}
729+
if(typeofinput==='object'){
730+
if(input.constructor&&input.constructor.name){
731+
return` Received an instance of ${input.constructor.name}`;
732+
}
733+
return` Received ${util.inspect(input,{depth: -1})}`;
734+
}
735+
letinspected=util.inspect(input,{colors: false});
736+
if(inspected.length>25)
737+
inspected=`${inspected.slice(0,25)}...`;
738+
return` Received type ${typeofinput} (${inspected})`;
739+
}
740+
721741
module.exports={
722742
allowGlobals,
723743
buildType,
@@ -735,6 +755,7 @@ module.exports ={
735755
hasIntl,
736756
hasCrypto,
737757
hasMultiLocalhost,
758+
invalidArgTypeHelper,
738759
isAIX,
739760
isAlive,
740761
isFreeBSD,

‎test/es-module/test-esm-loader-modulemap.js‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ common.expectsError(
2525
{
2626
code: 'ERR_INVALID_ARG_TYPE',
2727
type: TypeError,
28-
message: 'The "url" argument must be of type string. Received type number'
28+
message: 'The "url" argument must be of type string. Received type number'+
29+
' (1)'
2930
}
3031
);
3132

@@ -34,7 +35,8 @@ common.expectsError(
3435
{
3536
code: 'ERR_INVALID_ARG_TYPE',
3637
type: TypeError,
37-
message: 'The "url" argument must be of type string. Received type number'
38+
message: 'The "url" argument must be of type string. Received type number'+
39+
' (1)'
3840
}
3941
);
4042

@@ -43,8 +45,8 @@ common.expectsError(
4345
{
4446
code: 'ERR_INVALID_ARG_TYPE',
4547
type: TypeError,
46-
message: 'The "job" argument must be of type ModuleJob. '+
47-
'Received type string'
48+
message: 'The "job" argument must be an instance of ModuleJob. '+
49+
"Received type string ('notamodulejob')"
4850
}
4951
);
5052

@@ -53,6 +55,7 @@ common.expectsError(
5355
{
5456
code: 'ERR_INVALID_ARG_TYPE',
5557
type: TypeError,
56-
message: 'The "url" argument must be of type string. Received type number'
58+
message: 'The "url" argument must be of type string. Received type number'+
59+
' (1)'
5760
}
5861
);

‎test/internet/test-dns-promises-resolve.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const dnsPromises = require('dns').promises;
2626
code: 'ERR_INVALID_ARG_TYPE',
2727
type: TypeError,
2828
message: 'The "rrtype" argument must be of type string. '+
29-
`Received type ${typeofrrtype}`
29+
`Received type ${typeofrrtype} (${rrtype})`
3030
}
3131
);
3232
}

‎test/parallel/test-assert-async.js‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ promises.push(assert.rejects(
109109
assert.rejects('fail',{}),
110110
{
111111
code: 'ERR_INVALID_ARG_TYPE',
112-
message: 'The "promiseFn" argument must be one of type '+
113-
'Function or Promise. Received type string'
112+
message: 'The "promiseFn" argument must be of type function or an '+
113+
"instance of Promise. Received type string ('fail')"
114114
}
115115
));
116116

@@ -209,8 +209,8 @@ promises.push(assert.rejects(
209209
assert.doesNotReject(123),
210210
{
211211
code: 'ERR_INVALID_ARG_TYPE',
212-
message: 'The "promiseFn" argument must be one of type '+
213-
'Function or Promise. Received type number'
212+
message: 'The "promiseFn" argument must be of type '+
213+
'function or an instance of Promise. Received type number (123)'
214214
}
215215
));
216216
/* eslint-enable no-restricted-syntax */

‎test/parallel/test-assert.js‎

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -403,8 +403,8 @@ assert.throws(
403403
{
404404
code: 'ERR_INVALID_ARG_TYPE',
405405
type: TypeError,
406-
message: 'The "fn" argument must be of type Function. Received '+
407-
`type ${typeoffn}`
406+
message: 'The "fn" argument must be of type function.'+
407+
common.invalidArgTypeHelper(fn)
408408
}
409409
);
410410
};
@@ -473,8 +473,8 @@ assert.throws(() =>{
473473
{
474474
code: 'ERR_INVALID_ARG_TYPE',
475475
name: 'TypeError',
476-
message: 'The "options" argument must be of type Object. '+
477-
`Received type ${typeofinput}`
476+
message: 'The "options" argument must be of type object.'+
477+
common.invalidArgTypeHelper(input)
478478
});
479479
});
480480
}
@@ -920,8 +920,9 @@ common.expectsError(
920920
{
921921
code: 'ERR_INVALID_ARG_TYPE',
922922
type: TypeError,
923-
message: 'The "error" argument must be one of type Object, Error, '+
924-
'Function, or RegExp. Received type string'
923+
message: 'The "error" argument must be of type function or '+
924+
'an instance of Error, RegExp, or Object. Received type string '+
925+
"('Error message')"
925926
}
926927
);
927928

@@ -934,8 +935,9 @@ common.expectsError(
934935
()=>assert.throws(()=>{},input),
935936
{
936937
code: 'ERR_INVALID_ARG_TYPE',
937-
message: 'The "error" argument must be one of type Object, Error, '+
938-
`Function, or RegExp. Received type ${typeofinput}`
938+
message: 'The "error" argument must be of type function or '+
939+
'an instance of Error, RegExp, or Object.'+
940+
common.invalidArgTypeHelper(input)
939941
}
940942
);
941943
});
@@ -1013,8 +1015,8 @@ common.expectsError(
10131015
{
10141016
type: TypeError,
10151017
code: 'ERR_INVALID_ARG_TYPE',
1016-
message: 'The "expected" argument must be one of type Function or '+
1017-
'RegExp. Received type object'
1018+
message: 'The "expected" argument must be of type function or an '+
1019+
'instance of RegExp. Received an instance of Object'
10181020
}
10191021
);
10201022

‎test/parallel/test-buffer-alloc.js‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -967,19 +967,19 @@ common.expectsError(
967967
{
968968
code: 'ERR_INVALID_ARG_TYPE',
969969
type: TypeError,
970-
message: 'The "target" argument must be one of type Buffer or Uint8Array.'+
971-
' Received type undefined'
970+
message: 'The "target" argument must be an instance of Buffer or '+
971+
'Uint8Array. Received undefined'
972972
});
973973

974974
assert.throws(()=>Buffer.from(),{
975975
name: 'TypeError',
976-
message: 'The first argument must be one of type string, Buffer, '+
977-
'ArrayBuffer, Array, or Array-like Object. Received type undefined'
976+
message: 'The first argument must be of type string or an instance of '+
977+
'Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined'
978978
});
979979
assert.throws(()=>Buffer.from(null),{
980980
name: 'TypeError',
981-
message: 'The first argument must be one of type string, Buffer, '+
982-
'ArrayBuffer, Array, or Array-like Object. Received type object'
981+
message: 'The first argument must be of type string or an instance of '+
982+
'Buffer, ArrayBuffer, or Array or an Array-like Object. Received null'
983983
});
984984

985985
// Test prototype getters don't throw

‎test/parallel/test-buffer-arraybuffer.js‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ assert.throws(function(){
4343
},{
4444
code: 'ERR_INVALID_ARG_TYPE',
4545
name: 'TypeError',
46-
message: 'The first argument must be one of type string, Buffer,'+
47-
' ArrayBuffer, Array, or Array-like Object. Received type object'
46+
message: 'The first argument must be of type string or an instance of '+
47+
'Buffer, ArrayBuffer, or Array or an Array-like Object. Received '+
48+
'an instance of AB'
4849
});
4950

5051
// Test the byteOffset and length arguments

0 commit comments

Comments
(0)