Skip to content

Commit

Permalink
[Fix] ES5+: Use spec‑accurate IsCallable and IsConstructor impls
Browse files Browse the repository at this point in the history
Co-authored-by: Jordan Harband <[email protected]>
  • Loading branch information
ExE-Boss and ljharb committed May 18, 2020
1 parent 0c758b2 commit 371fc87
Show file tree
Hide file tree
Showing 14 changed files with 289 additions and 24 deletions.
11 changes: 10 additions & 1 deletion 2015/IsCallable.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
'use strict';

var hasSymbols = require('has-symbols')();
var callBound = require('../helpers/callBound.js');

var $toStr = callBound('%Object.prototype.toString%');

// http://www.ecma-international.org/ecma-262/5.1/#sec-9.11

module.exports = require('is-callable');
module.exports = function IsCallable(argument) {
// some older engines say that typeof /abc/ === 'function',
return typeof argument === 'function'
&& (hasSymbols || $toStr(argument) !== '[object RegExp]');
};
39 changes: 36 additions & 3 deletions 2015/IsConstructor.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,40 @@
'use strict';

var GetIntrinsic = require('../GetIntrinsic.js');

var $construct = GetIntrinsic('%Reflect.construct%', true);

var DefinePropertyOrThrow = require('./DefinePropertyOrThrow');
try {
DefinePropertyOrThrow({}, '', { '[[Get]]': function () {} });
} catch (e) {
// Accessor properties aren't supported
DefinePropertyOrThrow = null;
}

// https://www.ecma-international.org/ecma-262/6.0/#sec-isconstructor

module.exports = function IsConstructor(argument) {
return typeof argument === 'function' && !!argument.prototype; // unfortunately there's no way to truly check this without try/catch `new argument`
};
if (DefinePropertyOrThrow && $construct) {
var isConstructorMarker = {};
var badArrayLike = {};
DefinePropertyOrThrow(badArrayLike, 'length', {
'[[Get]]': function () {
throw isConstructorMarker;
},
'[[Enumerable]]': true
});

module.exports = function IsConstructor(argument) {
try {
// `Reflect.construct` invokes `IsConstructor(target)` before `Get(args, 'length')`:
$construct(argument, badArrayLike);
} catch (err) {
return err === isConstructorMarker;
}
};
} else {
module.exports = function IsConstructor(argument) {
// unfortunately there's no way to truly check this without try/catch `new argument` in old environments
return typeof argument === 'function' && !!argument.prototype;
};
}
11 changes: 10 additions & 1 deletion 2016/IsCallable.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
'use strict';

var hasSymbols = require('has-symbols')();
var callBound = require('../helpers/callBound.js');

var $toStr = callBound('%Object.prototype.toString%');

// http://www.ecma-international.org/ecma-262/5.1/#sec-9.11

module.exports = require('is-callable');
module.exports = function IsCallable(argument) {
// some older engines say that typeof /abc/ === 'function',
return typeof argument === 'function'
&& (hasSymbols || $toStr(argument) !== '[object RegExp]');
};
39 changes: 36 additions & 3 deletions 2016/IsConstructor.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,40 @@
'use strict';

var GetIntrinsic = require('../GetIntrinsic.js');

var $construct = GetIntrinsic('%Reflect.construct%', true);

var DefinePropertyOrThrow = require('./DefinePropertyOrThrow');
try {
DefinePropertyOrThrow({}, '', { '[[Get]]': function () {} });
} catch (e) {
// Accessor properties aren't supported
DefinePropertyOrThrow = null;
}

// https://www.ecma-international.org/ecma-262/6.0/#sec-isconstructor

module.exports = function IsConstructor(argument) {
return typeof argument === 'function' && !!argument.prototype; // unfortunately there's no way to truly check this without try/catch `new argument`
};
if (DefinePropertyOrThrow && $construct) {
var isConstructorMarker = {};
var badArrayLike = {};
DefinePropertyOrThrow(badArrayLike, 'length', {
'[[Get]]': function () {
throw isConstructorMarker;
},
'[[Enumerable]]': true
});

module.exports = function IsConstructor(argument) {
try {
// `Reflect.construct` invokes `IsConstructor(target)` before `Get(args, 'length')`:
$construct(argument, badArrayLike);
} catch (err) {
return err === isConstructorMarker;
}
};
} else {
module.exports = function IsConstructor(argument) {
// unfortunately there's no way to truly check this without try/catch `new argument` in old environments
return typeof argument === 'function' && !!argument.prototype;
};
}
11 changes: 10 additions & 1 deletion 2017/IsCallable.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
'use strict';

var hasSymbols = require('has-symbols')();
var callBound = require('../helpers/callBound.js');

var $toStr = callBound('%Object.prototype.toString%');

// http://www.ecma-international.org/ecma-262/5.1/#sec-9.11

module.exports = require('is-callable');
module.exports = function IsCallable(argument) {
// some older engines say that typeof /abc/ === 'function',
return typeof argument === 'function'
&& (hasSymbols || $toStr(argument) !== '[object RegExp]');
};
39 changes: 36 additions & 3 deletions 2017/IsConstructor.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,40 @@
'use strict';

var GetIntrinsic = require('../GetIntrinsic.js');

var $construct = GetIntrinsic('%Reflect.construct%', true);

var DefinePropertyOrThrow = require('./DefinePropertyOrThrow');
try {
DefinePropertyOrThrow({}, '', { '[[Get]]': function () {} });
} catch (e) {
// Accessor properties aren't supported
DefinePropertyOrThrow = null;
}

// https://www.ecma-international.org/ecma-262/6.0/#sec-isconstructor

module.exports = function IsConstructor(argument) {
return typeof argument === 'function' && !!argument.prototype; // unfortunately there's no way to truly check this without try/catch `new argument`
};
if (DefinePropertyOrThrow && $construct) {
var isConstructorMarker = {};
var badArrayLike = {};
DefinePropertyOrThrow(badArrayLike, 'length', {
'[[Get]]': function () {
throw isConstructorMarker;
},
'[[Enumerable]]': true
});

module.exports = function IsConstructor(argument) {
try {
// `Reflect.construct` invokes `IsConstructor(target)` before `Get(args, 'length')`:
$construct(argument, badArrayLike);
} catch (err) {
return err === isConstructorMarker;
}
};
} else {
module.exports = function IsConstructor(argument) {
// unfortunately there's no way to truly check this without try/catch `new argument` in old environments
return typeof argument === 'function' && !!argument.prototype;
};
}
11 changes: 10 additions & 1 deletion 2018/IsCallable.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
'use strict';

var hasSymbols = require('has-symbols')();
var callBound = require('../helpers/callBound.js');

var $toStr = callBound('%Object.prototype.toString%');

// http://www.ecma-international.org/ecma-262/5.1/#sec-9.11

module.exports = require('is-callable');
module.exports = function IsCallable(argument) {
// some older engines say that typeof /abc/ === 'function',
return typeof argument === 'function'
&& (hasSymbols || $toStr(argument) !== '[object RegExp]');
};
39 changes: 36 additions & 3 deletions 2018/IsConstructor.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,40 @@
'use strict';

var GetIntrinsic = require('../GetIntrinsic.js');

var $construct = GetIntrinsic('%Reflect.construct%', true);

var DefinePropertyOrThrow = require('./DefinePropertyOrThrow');
try {
DefinePropertyOrThrow({}, '', { '[[Get]]': function () {} });
} catch (e) {
// Accessor properties aren't supported
DefinePropertyOrThrow = null;
}

// https://www.ecma-international.org/ecma-262/6.0/#sec-isconstructor

module.exports = function IsConstructor(argument) {
return typeof argument === 'function' && !!argument.prototype; // unfortunately there's no way to truly check this without try/catch `new argument`
};
if (DefinePropertyOrThrow && $construct) {
var isConstructorMarker = {};
var badArrayLike = {};
DefinePropertyOrThrow(badArrayLike, 'length', {
'[[Get]]': function () {
throw isConstructorMarker;
},
'[[Enumerable]]': true
});

module.exports = function IsConstructor(argument) {
try {
// `Reflect.construct` invokes `IsConstructor(target)` before `Get(args, 'length')`:
$construct(argument, badArrayLike);
} catch (err) {
return err === isConstructorMarker;
}
};
} else {
module.exports = function IsConstructor(argument) {
// unfortunately there's no way to truly check this without try/catch `new argument` in old environments
return typeof argument === 'function' && !!argument.prototype;
};
}
11 changes: 10 additions & 1 deletion 2019/IsCallable.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
'use strict';

var hasSymbols = require('has-symbols')();
var callBound = require('../helpers/callBound.js');

var $toStr = callBound('%Object.prototype.toString%');

// http://www.ecma-international.org/ecma-262/5.1/#sec-9.11

module.exports = require('is-callable');
module.exports = function IsCallable(argument) {
// some older engines say that typeof /abc/ === 'function',
return typeof argument === 'function'
&& (hasSymbols || $toStr(argument) !== '[object RegExp]');
};
39 changes: 36 additions & 3 deletions 2019/IsConstructor.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,40 @@
'use strict';

var GetIntrinsic = require('../GetIntrinsic.js');

var $construct = GetIntrinsic('%Reflect.construct%', true);

var DefinePropertyOrThrow = require('./DefinePropertyOrThrow');
try {
DefinePropertyOrThrow({}, '', { '[[Get]]': function () {} });
} catch (e) {
// Accessor properties aren't supported
DefinePropertyOrThrow = null;
}

// https://www.ecma-international.org/ecma-262/6.0/#sec-isconstructor

module.exports = function IsConstructor(argument) {
return typeof argument === 'function' && !!argument.prototype; // unfortunately there's no way to truly check this without try/catch `new argument`
};
if (DefinePropertyOrThrow && $construct) {
var isConstructorMarker = {};
var badArrayLike = {};
DefinePropertyOrThrow(badArrayLike, 'length', {
'[[Get]]': function () {
throw isConstructorMarker;
},
'[[Enumerable]]': true
});

module.exports = function IsConstructor(argument) {
try {
// `Reflect.construct` invokes `IsConstructor(target)` before `Get(args, 'length')`:
$construct(argument, badArrayLike);
} catch (err) {
return err === isConstructorMarker;
}
};
} else {
module.exports = function IsConstructor(argument) {
// unfortunately there's no way to truly check this without try/catch `new argument` in old environments
return typeof argument === 'function' && !!argument.prototype;
};
}
11 changes: 10 additions & 1 deletion 5/IsCallable.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
'use strict';

var hasSymbols = require('has-symbols')();
var callBound = require('../helpers/callBound.js');

var $toStr = callBound('%Object.prototype.toString%');

// http://www.ecma-international.org/ecma-262/5.1/#sec-9.11

module.exports = require('is-callable');
module.exports = function IsCallable(argument) {
// some older engines say that typeof /abc/ === 'function',
return typeof argument === 'function'
&& (hasSymbols || $toStr(argument) !== '[object RegExp]');
};
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.1.5",
"is-regex": "^1.0.5",
"object-inspect": "^1.7.0",
"object-keys": "^1.1.1",
Expand Down
18 changes: 17 additions & 1 deletion test/es5.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,26 @@ test('CheckObjectCoercible', function (t) {

test('IsCallable', function (t) {
t.equal(true, ES.IsCallable(function () {}), 'function is callable');
var nonCallables = [/a/g, {}, Object.prototype, NaN].concat(v.primitives);
var nonCallables = [/a/g, {}, Object.prototype, NaN].concat(v.nonFunctions);
forEach(nonCallables, function (nonCallable) {
t.equal(false, ES.IsCallable(nonCallable), debug(nonCallable) + ' is not callable');
});

var foo;
try {
foo = Function('return () => {}')(); // eslint-disable-line no-new-func
t.equal(ES.IsCallable(foo), true, 'arrow function is callable');
} catch (e) {
t.comment('SKIP: arrow function syntax not supported.');
}

try {
foo = Function('return class Foo {}')(); // eslint-disable-line no-new-func
t.equal(ES.IsCallable(foo), true, 'class is callable');
} catch (e) {
t.comment('SKIP: class syntax not supported.');
}

t.end();
});

Expand Down
Loading

0 comments on commit 371fc87

Please sign in to comment.