├── .editorconfig ├── .eslintrc ├── .github ├── FUNDING.yml └── workflows │ ├── node-aught.yml │ ├── node-pretest.yml │ ├── node-tens.yml │ ├── rebase.yml │ └── require-allow-edits.yml ├── .gitignore ├── .npmrc ├── .nycrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.js ├── package.json └── test └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | max_line_length = 150 11 | 12 | [CHANGELOG.md] 13 | indent_style = space 14 | indent_size = 2 15 | max_line_length = off 16 | 17 | [README.md] 18 | indent_style = off 19 | indent_size = off 20 | max_line_length = off 21 | 22 | [*.json] 23 | max_line_length = off 24 | 25 | [Makefile] 26 | max_line_length = off 27 | 28 | [coverage*/**/*] 29 | indent_style = off 30 | indent_size = off 31 | max_line_length = off 32 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | 4 | "extends": "@ljharb", 5 | 6 | "rules": { 7 | "id-length": 0, 8 | "max-statements-per-line": [2, { "max": 2 }], 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [ljharb] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: npm/is-callable 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/node-aught.yml: -------------------------------------------------------------------------------- 1 | name: 'Tests: node.js < 10' 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | tests: 7 | uses: ljharb/actions/.github/workflows/node.yml@main 8 | with: 9 | range: '< 10' 10 | type: minors 11 | command: npm run tests-only 12 | 13 | node: 14 | name: 'node < 10' 15 | needs: [tests] 16 | runs-on: ubuntu-latest 17 | steps: 18 | - run: 'echo tests completed' 19 | -------------------------------------------------------------------------------- /.github/workflows/node-pretest.yml: -------------------------------------------------------------------------------- 1 | name: 'Tests: pretest/posttest' 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | tests: 7 | uses: ljharb/actions/.github/workflows/pretest.yml@main 8 | -------------------------------------------------------------------------------- /.github/workflows/node-tens.yml: -------------------------------------------------------------------------------- 1 | name: 'Tests: node.js >= 10' 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | tests: 7 | uses: ljharb/actions/.github/workflows/node.yml@main 8 | with: 9 | range: '>= 10' 10 | type: minors 11 | command: npm run tests-only 12 | 13 | node: 14 | name: 'node >= 10' 15 | needs: [tests] 16 | runs-on: ubuntu-latest 17 | steps: 18 | - run: 'echo tests completed' 19 | -------------------------------------------------------------------------------- /.github/workflows/rebase.yml: -------------------------------------------------------------------------------- 1 | name: Automatic Rebase 2 | 3 | on: [pull_request_target] 4 | 5 | jobs: 6 | _: 7 | uses: ljharb/actions/.github/workflows/rebase.yml@main 8 | secrets: 9 | token: ${{ secrets.GITHUB_TOKEN }} 10 | -------------------------------------------------------------------------------- /.github/workflows/require-allow-edits.yml: -------------------------------------------------------------------------------- 1 | name: Require “Allow Edits” 2 | 3 | on: [pull_request_target] 4 | 5 | jobs: 6 | _: 7 | name: "Require “Allow Edits”" 8 | 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: ljharb/require-allow-edits@main 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage/ 15 | .nyc_output/ 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # Compiled binary addons (http://nodejs.org/api/addons.html) 21 | build/Release 22 | 23 | # Dependency directory 24 | # Commenting this out is preferred by some people, see 25 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 26 | node_modules 27 | 28 | # Users Environment Variables 29 | .lock-wscript 30 | 31 | # Only apps should have lockfiles 32 | yarn.lock 33 | package-lock.json 34 | npm-shrinkwrap.json 35 | 36 | .npmignore 37 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | allow-same-version=true 3 | message=v%s 4 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "all": true, 3 | "check-coverage": false, 4 | "reporter": ["text-summary", "text", "html", "json"], 5 | "exclude": [ 6 | "coverage", 7 | "test" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [v1.2.7](https://github.com/inspect-js/is-callable/compare/v1.2.6...v1.2.7) - 2022-09-23 9 | 10 | ### Commits 11 | 12 | - [Fix] recognize `document.all` in IE 6-10 [`06c1db2`](https://github.com/inspect-js/is-callable/commit/06c1db2b9b2e0f28428e1293eb572f8f93871ec7) 13 | - [Tests] improve logic for FF 20-35 [`0f7d9b9`](https://github.com/inspect-js/is-callable/commit/0f7d9b9c7fe149ca87e71f0a125ade251a6a578c) 14 | - [Fix] handle `document.all` in FF 27 (and +, probably) [`696c661`](https://github.com/inspect-js/is-callable/commit/696c661b8c0810c2d05ab172f1607f4e77ddf81e) 15 | - [Tests] fix proxy tests in FF 42-63 [`985df0d`](https://github.com/inspect-js/is-callable/commit/985df0dd36f8cfe6f1993657b7c0f4cfc19dae30) 16 | - [readme] update tested browsers [`389e919`](https://github.com/inspect-js/is-callable/commit/389e919493b1cb2010126b0411e5291bf76169bd) 17 | - [Fix] detect `document.all` in Opera 12.16 [`b9f1022`](https://github.com/inspect-js/is-callable/commit/b9f1022b3d7e466b7f09080bd64c253caf644325) 18 | - [Fix] HTML elements: properly report as callable in Opera 12.16 [`17391fe`](https://github.com/inspect-js/is-callable/commit/17391fe02b895777c4337be28dca3b364b743b34) 19 | - [Tests] fix inverted logic in FF3 test [`056ebd4`](https://github.com/inspect-js/is-callable/commit/056ebd48790f46ca18ff5b12f51b44c08ccc3595) 20 | 21 | ## [v1.2.6](https://github.com/inspect-js/is-callable/compare/v1.2.5...v1.2.6) - 2022-09-14 22 | 23 | ### Commits 24 | 25 | - [Fix] work for `document.all` in Firefox 3 and IE 6-8 [`015132a`](https://github.com/inspect-js/is-callable/commit/015132aaef886ec777b5b3593ef4ce461dd0c7d4) 26 | - [Test] skip function toString check for nullish values [`8698116`](https://github.com/inspect-js/is-callable/commit/8698116f95eb59df8b48ec8e4585fc1cdd8cae9f) 27 | - [readme] add "supported engines" section [`0442207`](https://github.com/inspect-js/is-callable/commit/0442207a89a1554d41ba36daf21862ef7ccbd500) 28 | - [Tests] skip one of the fixture objects in FF 3.6 [`a501141`](https://github.com/inspect-js/is-callable/commit/a5011410bc6edb276c6ec8b47ce5c5d83c4bee15) 29 | - [Tests] allow `class` constructor tests to fail in FF v45 - v54, which has undetectable classes [`b12e4a4`](https://github.com/inspect-js/is-callable/commit/b12e4a4d8c438678bd7710f9f896680150766b51) 30 | - [Fix] Safari 4: regexes should not be considered callable [`4b732ff`](https://github.com/inspect-js/is-callable/commit/4b732ffa34346db3f0193ea4e46b7d4e637e6c82) 31 | - [Fix] properly recognize `document.all` in Safari 4 [`3193735`](https://github.com/inspect-js/is-callable/commit/319373525dc4603346661641840cd9a3e0613136) 32 | 33 | ## [v1.2.5](https://github.com/inspect-js/is-callable/compare/v1.2.4...v1.2.5) - 2022-09-11 34 | 35 | ### Commits 36 | 37 | - [actions] reuse common workflows [`5bb4b32`](https://github.com/inspect-js/is-callable/commit/5bb4b32dc93987328ab4f396601f751c4a7abd62) 38 | - [meta] better `eccheck` command [`b9bd597`](https://github.com/inspect-js/is-callable/commit/b9bd597322b6e3a24c74c09881ca73e1d9f9f485) 39 | - [meta] use `npmignore` to autogenerate an npmignore file [`3192d38`](https://github.com/inspect-js/is-callable/commit/3192d38527c7fc461d05d5aa93d47628e658bc45) 40 | - [Fix] for HTML constructors, always use `tryFunctionObject` even in pre-toStringTag browsers [`3076ea2`](https://github.com/inspect-js/is-callable/commit/3076ea21d1f6ecc1cb711dcf1da08f257892c72b) 41 | - [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `available-typed-arrays`, `object-inspect`, `safe-publish-latest`, `tape` [`8986746`](https://github.com/inspect-js/is-callable/commit/89867464c42adc5cd375ee074a4574b0295442cb) 42 | - [meta] add `auto-changelog` [`7dda9d0`](https://github.com/inspect-js/is-callable/commit/7dda9d04e670a69ae566c8fa596da4ff4371e615) 43 | - [Fix] properly report `document.all` [`da90b2b`](https://github.com/inspect-js/is-callable/commit/da90b2b68dc4f33702c2e01ad07b4f89bcb60984) 44 | - [actions] update codecov uploader [`c8f847c`](https://github.com/inspect-js/is-callable/commit/c8f847c90e04e54ff73c7cfae86e96e94990e324) 45 | - [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `aud`, `object-inspect`, `tape` [`899ae00`](https://github.com/inspect-js/is-callable/commit/899ae00b6abd10d81fc8bc7f02b345fd885d5f56) 46 | - [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `es-value-fixtures`, `object-inspect`, `tape` [`344e913`](https://github.com/inspect-js/is-callable/commit/344e913b149609bf741aa7345fa32dc0b90d8893) 47 | - [meta] remove greenkeeper config [`737dce5`](https://github.com/inspect-js/is-callable/commit/737dce5590b1abb16183a63cb9d7d26920b3b394) 48 | - [meta] npmignore coverage output [`680a883`](https://github.com/inspect-js/is-callable/commit/680a8839071bf36a419fe66e1ced7a3303c27b28) 49 | 50 | 51 | 1.2.4 / 2021-08-05 52 | ================= 53 | * [Fix] use `has-tostringtag` approach to behave correctly in the presence of symbol shams 54 | * [readme] fix repo URLs 55 | * [readme] add actions and codecov badges 56 | * [readme] remove defunct badges 57 | * [meta] ignore eclint checking coverage output 58 | * [meta] use `prepublishOnly` script for npm 7+ 59 | * [actions] use `node/install` instead of `node/run`; use `codecov` action 60 | * [actions] remove unused workflow file 61 | * [Tests] run `nyc` on all tests; use `tape` runner 62 | * [Tests] use `available-typed-arrays`, `for-each`, `has-symbols`, `object-inspect` 63 | * [Dev Deps] update `available-typed-arrays`, `eslint`, `@ljharb/eslint-config`, `aud`, `object-inspect`, `tape` 64 | 65 | 1.2.3 / 2021-01-31 66 | ================= 67 | * [Fix] `document.all` is callable (do not use `document.all`!) 68 | * [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `aud`, `tape` 69 | * [Tests] migrate tests to Github Actions 70 | * [actions] add "Allow Edits" workflow 71 | * [actions] switch Automatic Rebase workflow to `pull_request_target` event 72 | 73 | 1.2.2 / 2020-09-21 74 | ================= 75 | * [Fix] include actual fix from 579179e 76 | * [Dev Deps] update `eslint` 77 | 78 | 1.2.1 / 2020-09-09 79 | ================= 80 | * [Fix] phantomjs‘ Reflect.apply does not throw properly on a bad array-like 81 | * [Dev Deps] update `eslint`, `@ljharb/eslint-config` 82 | * [meta] fix eclint error 83 | 84 | 1.2.0 / 2020-06-02 85 | ================= 86 | * [New] use `Reflect.apply`‑based callability detection 87 | * [readme] add install instructions (#55) 88 | * [meta] only run `aud` on prod deps 89 | * [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `tape`, `make-arrow-function`, `make-generator-function`; add `aud`, `safe-publish-latest`, `make-async-function` 90 | * [Tests] add tests for function proxies (#53, #25) 91 | 92 | 1.1.5 / 2019-12-18 93 | ================= 94 | * [meta] remove unused Makefile and associated utilities 95 | * [meta] add `funding` field; add FUNDING.yml 96 | * [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `semver`, `tape`, `covert`, `rimraf` 97 | * [Tests] use shared travis configs 98 | * [Tests] use `eccheck` over `editorconfig-tools` 99 | * [Tests] use `npx aud` instead of `nsp` or `npm audit` with hoops 100 | * [Tests] remove `jscs` 101 | * [actions] add automatic rebasing / merge commit blocking 102 | 103 | 1.1.4 / 2018-07-02 104 | ================= 105 | * [Fix] improve `class` and arrow function detection (#30, #31) 106 | * [Tests] on all latest node minors; improve matrix 107 | * [Dev Deps] update all dev deps 108 | 109 | 1.1.3 / 2016-02-27 110 | ================= 111 | * [Fix] ensure “class “ doesn’t screw up “class” detection 112 | * [Tests] up to `node` `v5.7`, `v4.3` 113 | * [Dev Deps] update to `eslint` v2, `@ljharb/eslint-config`, `jscs` 114 | 115 | 1.1.2 / 2016-01-15 116 | ================= 117 | * [Fix] Make sure comments don’t screw up “class” detection (#4) 118 | * [Tests] up to `node` `v5.3` 119 | * [Tests] Add `parallelshell`, run both `--es-staging` and stock tests at once 120 | * [Dev Deps] update `tape`, `jscs`, `nsp`, `eslint`, `@ljharb/eslint-config` 121 | * [Refactor] convert `isNonES6ClassFn` into `isES6ClassFn` 122 | 123 | 1.1.1 / 2015-11-30 124 | ================= 125 | * [Fix] do not throw when a non-function has a function in its [[Prototype]] (#2) 126 | * [Dev Deps] update `tape`, `eslint`, `@ljharb/eslint-config`, `jscs`, `nsp`, `semver` 127 | * [Tests] up to `node` `v5.1` 128 | * [Tests] no longer allow node 0.8 to fail. 129 | * [Tests] fix npm upgrades in older nodes 130 | 131 | 1.1.0 / 2015-10-02 132 | ================= 133 | * [Fix] Some browsers report TypedArray constructors as `typeof object` 134 | * [New] return false for "class" constructors, when possible. 135 | * [Tests] up to `io.js` `v3.3`, `node` `v4.1` 136 | * [Dev Deps] update `eslint`, `editorconfig-tools`, `nsp`, `tape`, `semver`, `jscs`, `covert`, `make-arrow-function` 137 | * [Docs] Switch from vb.teelaun.ch to versionbadg.es for the npm version badge SVG 138 | 139 | 1.0.4 / 2015-01-30 140 | ================= 141 | * If @@toStringTag is not present, use the old-school Object#toString test. 142 | 143 | 1.0.3 / 2015-01-29 144 | ================= 145 | * Add tests to ensure arrow functions are callable. 146 | * Refactor to aid optimization of non-try/catch code. 147 | 148 | 1.0.2 / 2015-01-29 149 | ================= 150 | * Fix broken package.json 151 | 152 | 1.0.1 / 2015-01-29 153 | ================= 154 | * Add early exit for typeof not "function" 155 | 156 | 1.0.0 / 2015-01-29 157 | ================= 158 | * Initial release. 159 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jordan Harband 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # is-callable [![Version Badge][2]][1] 2 | 3 | [![github actions][actions-image]][actions-url] 4 | [![coverage][codecov-image]][codecov-url] 5 | [![dependency status][5]][6] 6 | [![dev dependency status][7]][8] 7 | [![License][license-image]][license-url] 8 | [![Downloads][downloads-image]][downloads-url] 9 | 10 | [![npm badge][11]][1] 11 | 12 | Is this JS value callable? Works with Functions and GeneratorFunctions, despite ES6 @@toStringTag. 13 | 14 | ## Supported engines 15 | Automatically tested in every minor version of node. 16 | 17 | Manually tested in: 18 | - Safari: v4 - v15 (4, 5, 5.1, 6.0.5, 6.2, 7.1, 8, 9.1.3, 10.1.2, 11.1.2, 12.1, 13.1.2, 14.1.2, 15.3, 15.6.1) 19 | - Note: Safari 9 has `class`, but `Function.prototype.toString` hides that progeny and makes them look like functions, so `class` constructors will be reported by this package as callable, when they are not in fact callable. 20 | - Chrome: v15 - v81, v83 - v106(every integer version) 21 | - Note: This includes Edge v80+ and Opera v15+, which matches Chrome 22 | - Firefox: v3, v3.6, v4 - v105 (every integer version) 23 | - Note: v45 - v54 has `class`, but `Function.prototype.toString` hides that progeny and makes them look like functions, so `class` constructors will be reported by this package as callable, when they are not in fact callable. 24 | - Note: in v42 - v63, `Function.prototype.toString` throws on HTML element constructors, or a Proxy to a function 25 | - Note: in v20 - v35, HTML element constructors are not callable, despite having typeof `function`. 26 | - Note: in v19, `document.all` is not callable. 27 | - IE: v6 - v11(every integer version 28 | - Opera: v11.1, v11.5, v11.6, v12.1, v12.14, v12.15, v12.16, v15+ v15+ matches Chrome 29 | 30 | ## Example 31 | 32 | ```js 33 | var isCallable = require('is-callable'); 34 | var assert = require('assert'); 35 | 36 | assert.notOk(isCallable(undefined)); 37 | assert.notOk(isCallable(null)); 38 | assert.notOk(isCallable(false)); 39 | assert.notOk(isCallable(true)); 40 | assert.notOk(isCallable([])); 41 | assert.notOk(isCallable({})); 42 | assert.notOk(isCallable(/a/g)); 43 | assert.notOk(isCallable(new RegExp('a', 'g'))); 44 | assert.notOk(isCallable(new Date())); 45 | assert.notOk(isCallable(42)); 46 | assert.notOk(isCallable(NaN)); 47 | assert.notOk(isCallable(Infinity)); 48 | assert.notOk(isCallable(new Number(42))); 49 | assert.notOk(isCallable('foo')); 50 | assert.notOk(isCallable(Object('foo'))); 51 | 52 | assert.ok(isCallable(function () {})); 53 | assert.ok(isCallable(function* () {})); 54 | assert.ok(isCallable(x => x * x)); 55 | ``` 56 | 57 | ## Install 58 | 59 | Install with 60 | 61 | ``` 62 | npm install is-callable 63 | ``` 64 | 65 | ## Tests 66 | 67 | Simply clone the repo, `npm install`, and run `npm test` 68 | 69 | [1]: https://npmjs.org/package/is-callable 70 | [2]: https://versionbadg.es/inspect-js/is-callable.svg 71 | [5]: https://david-dm.org/inspect-js/is-callable.svg 72 | [6]: https://david-dm.org/inspect-js/is-callable 73 | [7]: https://david-dm.org/inspect-js/is-callable/dev-status.svg 74 | [8]: https://david-dm.org/inspect-js/is-callable#info=devDependencies 75 | [11]: https://nodei.co/npm/is-callable.png?downloads=true&stars=true 76 | [license-image]: https://img.shields.io/npm/l/is-callable.svg 77 | [license-url]: LICENSE 78 | [downloads-image]: https://img.shields.io/npm/dm/is-callable.svg 79 | [downloads-url]: https://npm-stat.com/charts.html?package=is-callable 80 | [codecov-image]: https://codecov.io/gh/inspect-js/is-callable/branch/main/graphs/badge.svg 81 | [codecov-url]: https://app.codecov.io/gh/inspect-js/is-callable/ 82 | [actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/inspect-js/is-callable 83 | [actions-url]: https://github.com/inspect-js/is-callable/actions 84 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fnToStr = Function.prototype.toString; 4 | var reflectApply = typeof Reflect === 'object' && Reflect !== null && Reflect.apply; 5 | var badArrayLike; 6 | var isCallableMarker; 7 | if (typeof reflectApply === 'function' && typeof Object.defineProperty === 'function') { 8 | try { 9 | badArrayLike = Object.defineProperty({}, 'length', { 10 | get: function () { 11 | throw isCallableMarker; 12 | } 13 | }); 14 | isCallableMarker = {}; 15 | // eslint-disable-next-line no-throw-literal 16 | reflectApply(function () { throw 42; }, null, badArrayLike); 17 | } catch (_) { 18 | if (_ !== isCallableMarker) { 19 | reflectApply = null; 20 | } 21 | } 22 | } else { 23 | reflectApply = null; 24 | } 25 | 26 | var constructorRegex = /^\s*class\b/; 27 | var isES6ClassFn = function isES6ClassFunction(value) { 28 | try { 29 | var fnStr = fnToStr.call(value); 30 | return constructorRegex.test(fnStr); 31 | } catch (e) { 32 | return false; // not a function 33 | } 34 | }; 35 | 36 | var tryFunctionObject = function tryFunctionToStr(value) { 37 | try { 38 | if (isES6ClassFn(value)) { return false; } 39 | fnToStr.call(value); 40 | return true; 41 | } catch (e) { 42 | return false; 43 | } 44 | }; 45 | var toStr = Object.prototype.toString; 46 | var objectClass = '[object Object]'; 47 | var fnClass = '[object Function]'; 48 | var genClass = '[object GeneratorFunction]'; 49 | var ddaClass = '[object HTMLAllCollection]'; // IE 11 50 | var ddaClass2 = '[object HTML document.all class]'; 51 | var ddaClass3 = '[object HTMLCollection]'; // IE 9-10 52 | var hasToStringTag = typeof Symbol === 'function' && !!Symbol.toStringTag; // better: use `has-tostringtag` 53 | 54 | var isIE68 = !(0 in [,]); // eslint-disable-line no-sparse-arrays, comma-spacing 55 | 56 | var isDDA = function isDocumentDotAll() { return false; }; 57 | if (typeof document === 'object') { 58 | // Firefox 3 canonicalizes DDA to undefined when it's not accessed directly 59 | var all = document.all; 60 | if (toStr.call(all) === toStr.call(document.all)) { 61 | isDDA = function isDocumentDotAll(value) { 62 | /* globals document: false */ 63 | // in IE 6-8, typeof document.all is "object" and it's truthy 64 | if ((isIE68 || !value) && (typeof value === 'undefined' || typeof value === 'object')) { 65 | try { 66 | var str = toStr.call(value); 67 | return ( 68 | str === ddaClass 69 | || str === ddaClass2 70 | || str === ddaClass3 // opera 12.16 71 | || str === objectClass // IE 6-8 72 | ) && value('') == null; // eslint-disable-line eqeqeq 73 | } catch (e) { /**/ } 74 | } 75 | return false; 76 | }; 77 | } 78 | } 79 | 80 | module.exports = reflectApply 81 | ? function isCallable(value) { 82 | if (isDDA(value)) { return true; } 83 | if (!value) { return false; } 84 | if (typeof value !== 'function' && typeof value !== 'object') { return false; } 85 | try { 86 | reflectApply(value, null, badArrayLike); 87 | } catch (e) { 88 | if (e !== isCallableMarker) { return false; } 89 | } 90 | return !isES6ClassFn(value) && tryFunctionObject(value); 91 | } 92 | : function isCallable(value) { 93 | if (isDDA(value)) { return true; } 94 | if (!value) { return false; } 95 | if (typeof value !== 'function' && typeof value !== 'object') { return false; } 96 | if (hasToStringTag) { return tryFunctionObject(value); } 97 | if (isES6ClassFn(value)) { return false; } 98 | var strClass = toStr.call(value); 99 | if (strClass !== fnClass && strClass !== genClass && !(/^\[object HTML/).test(strClass)) { return false; } 100 | return tryFunctionObject(value); 101 | }; 102 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "is-callable", 3 | "version": "1.2.7", 4 | "author": { 5 | "name": "Jordan Harband", 6 | "email": "ljharb@gmail.com", 7 | "url": "http://ljharb.codes" 8 | }, 9 | "funding": { 10 | "url": "https://github.com/sponsors/ljharb" 11 | }, 12 | "contributors": [ 13 | { 14 | "name": "Jordan Harband", 15 | "email": "ljharb@gmail.com", 16 | "url": "http://ljharb.codes" 17 | } 18 | ], 19 | "description": "Is this JS value callable? Works with Functions and GeneratorFunctions, despite ES6 @@toStringTag.", 20 | "license": "MIT", 21 | "main": "index.js", 22 | "scripts": { 23 | "prepack": "npmignore --auto --commentLines=autogenerated", 24 | "version": "auto-changelog && git add CHANGELOG.md", 25 | "postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\"", 26 | "prepublishOnly": "safe-publish-latest", 27 | "prepublish": "not-in-publish || npm run prepublishOnly", 28 | "pretest": "npm run --silent lint", 29 | "test": "npm run tests-only --", 30 | "posttest": "aud --production", 31 | "tests-only": "nyc tape 'test/**/*.js'", 32 | "prelint": "eclint check $(git ls-files | xargs find 2> /dev/null | grep -vE 'node_modules|\\.git')", 33 | "lint": "eslint --ext=js,mjs ." 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git://github.com/inspect-js/is-callable.git" 38 | }, 39 | "keywords": [ 40 | "Function", 41 | "function", 42 | "callable", 43 | "generator", 44 | "generator function", 45 | "arrow", 46 | "arrow function", 47 | "ES6", 48 | "toStringTag", 49 | "@@toStringTag" 50 | ], 51 | "devDependencies": { 52 | "@ljharb/eslint-config": "^21.0.0", 53 | "aud": "^2.0.1", 54 | "auto-changelog": "^2.4.0", 55 | "available-typed-arrays": "^1.0.5", 56 | "eclint": "^2.8.1", 57 | "es-value-fixtures": "^1.4.2", 58 | "eslint": "=8.8.0", 59 | "for-each": "^0.3.3", 60 | "has-tostringtag": "^1.0.0", 61 | "in-publish": "^2.0.1", 62 | "make-arrow-function": "^1.2.0", 63 | "make-async-function": "^1.0.0", 64 | "make-generator-function": "^2.0.0", 65 | "npmignore": "^0.3.0", 66 | "nyc": "^10.3.2", 67 | "object-inspect": "^1.12.2", 68 | "rimraf": "^2.7.1", 69 | "safe-publish-latest": "^2.0.0", 70 | "tape": "^5.6.1" 71 | }, 72 | "testling": { 73 | "files": "test/index.js", 74 | "browsers": [ 75 | "iexplore/6.0..latest", 76 | "firefox/3.0..6.0", 77 | "firefox/15.0..latest", 78 | "firefox/nightly", 79 | "chrome/4.0..10.0", 80 | "chrome/20.0..latest", 81 | "chrome/canary", 82 | "opera/10.0..latest", 83 | "opera/next", 84 | "safari/4.0..latest", 85 | "ipad/6.0..latest", 86 | "iphone/6.0..latest", 87 | "android-browser/4.2" 88 | ] 89 | }, 90 | "engines": { 91 | "node": ">= 0.4" 92 | }, 93 | "auto-changelog": { 94 | "output": "CHANGELOG.md", 95 | "template": "keepachangelog", 96 | "unreleased": false, 97 | "commitLimit": false, 98 | "backfillLimit": false, 99 | "hideCredit": true, 100 | "startingVersion": "v1.2.5" 101 | }, 102 | "publishConfig": { 103 | "ignore": [ 104 | ".github/workflows" 105 | ] 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint no-magic-numbers: 1 */ 4 | 5 | var test = require('tape'); 6 | var isCallable = require('../'); 7 | var hasToStringTag = require('has-tostringtag/shams')(); 8 | var v = require('es-value-fixtures'); 9 | var forEach = require('for-each'); 10 | var inspect = require('object-inspect'); 11 | var typedArrayNames = require('available-typed-arrays')(); 12 | var generators = require('make-generator-function')(); 13 | var arrows = require('make-arrow-function').list(); 14 | var asyncs = require('make-async-function').list(); 15 | var weirdlyCommentedArrowFn; 16 | try { 17 | /* eslint-disable no-new-func */ 18 | weirdlyCommentedArrowFn = Function('return cl/*/**/=>/**/ass - 1;')(); 19 | /* eslint-enable no-new-func */ 20 | } catch (e) { /**/ } 21 | 22 | var isIE68 = !(0 in [undefined]); 23 | var isFirefox = typeof window !== 'undefined' && ('netscape' in window) && (/ rv:/).test(navigator.userAgent); 24 | var fnToStringCoerces; 25 | try { 26 | Function.prototype.toString.call(v.uncoercibleFnObject); 27 | fnToStringCoerces = true; 28 | } catch (e) { 29 | fnToStringCoerces = false; 30 | } 31 | 32 | var noop = function () {}; 33 | var classFake = function classFake() { }; // eslint-disable-line func-name-matching 34 | var returnClass = function () { return ' class '; }; 35 | var return3 = function () { return 3; }; 36 | /* for coverage */ 37 | noop(); 38 | classFake(); 39 | returnClass(); 40 | return3(); 41 | /* end for coverage */ 42 | 43 | var proxy; 44 | if (typeof Proxy === 'function') { 45 | try { 46 | proxy = new Proxy(function () {}, {}); 47 | // for coverage 48 | proxy(); 49 | String(proxy); 50 | } catch (_) { 51 | // Older engines throw a `TypeError` when `Function.prototype.toString` is called on a Proxy object. 52 | proxy = null; 53 | } 54 | } 55 | 56 | var invokeFunction = function invokeFunctionString(str) { 57 | var result; 58 | try { 59 | /* eslint-disable no-new-func */ 60 | var fn = Function(str); 61 | /* eslint-enable no-new-func */ 62 | result = fn(); 63 | } catch (e) {} 64 | return result; 65 | }; 66 | 67 | var classConstructor = invokeFunction('"use strict"; return class Foo {}'); 68 | var hasDetectableClasses = classConstructor && Function.prototype.toString.call(classConstructor) === 'class Foo {}'; 69 | 70 | var commentedClass = invokeFunction('"use strict"; return class/*kkk*/\n//blah\n Bar\n//blah\n {}'); 71 | var commentedClassOneLine = invokeFunction('"use strict"; return class/**/A{}'); 72 | var classAnonymous = invokeFunction('"use strict"; return class{}'); 73 | var classAnonymousCommentedOneLine = invokeFunction('"use strict"; return class/*/*/{}'); 74 | 75 | test('not callables', function (t) { 76 | t.notOk(isCallable(), 'implicit undefined is not callable'); 77 | 78 | forEach(v.nonFunctions.concat([ 79 | Object(42), 80 | Object('foo'), 81 | NaN, 82 | [], 83 | /a/g, 84 | new RegExp('a', 'g'), 85 | new Date() 86 | ]), function (nonFunction) { 87 | if (fnToStringCoerces && nonFunction === v.coercibleFnObject) { 88 | t.comment('FF 3.6 has a Function toString that coerces its receiver, so this test is skipped'); 89 | return; 90 | } 91 | if (nonFunction != null) { // eslint-disable-line eqeqeq 92 | if (isFirefox) { 93 | // Firefox 3 throws some kind of *object* here instead of a proper error 94 | t['throws']( 95 | function () { Function.prototype.toString.call(nonFunction); }, 96 | inspect(nonFunction) + ' can not be used with Function toString' 97 | ); 98 | } else { 99 | t['throws']( 100 | function () { Function.prototype.toString.call(nonFunction); }, 101 | TypeError, 102 | inspect(nonFunction) + ' can not be used with Function toString' 103 | ); 104 | } 105 | } 106 | t.equal(isCallable(nonFunction), false, inspect(nonFunction) + ' is not callable'); 107 | }); 108 | 109 | t.test('non-function with function in its [[Prototype]] chain', function (st) { 110 | var Foo = function Bar() {}; 111 | Foo.prototype = noop; 112 | st.equal(isCallable(Foo), true, 'sanity check: Foo is callable'); 113 | st.equal(isCallable(new Foo()), false, 'instance of Foo is not callable'); 114 | st.end(); 115 | }); 116 | 117 | t.end(); 118 | }); 119 | 120 | test('@@toStringTag', { skip: !hasToStringTag }, function (t) { 121 | var fakeFunction = { 122 | toString: function () { return String(return3); }, 123 | valueOf: return3 124 | }; 125 | fakeFunction[Symbol.toStringTag] = 'Function'; 126 | t.equal(String(fakeFunction), String(return3)); 127 | t.equal(Number(fakeFunction), return3()); 128 | t.notOk(isCallable(fakeFunction), 'fake Function with @@toStringTag "Function" is not callable'); 129 | t.end(); 130 | }); 131 | 132 | test('Functions', function (t) { 133 | t.ok(isCallable(noop), 'function is callable'); 134 | t.ok(isCallable(classFake), 'function with name containing "class" is callable'); 135 | t.ok(isCallable(returnClass), 'function with string " class " is callable'); 136 | t.ok(isCallable(isCallable), 'isCallable is callable'); 137 | t.end(); 138 | }); 139 | 140 | test('Typed Arrays', { skip: typedArrayNames.length === 0 }, function (st) { 141 | forEach(typedArrayNames, function (typedArray) { 142 | st.ok(isCallable(global[typedArray]), typedArray + ' is callable'); 143 | }); 144 | st.end(); 145 | }); 146 | 147 | test('Generators', { skip: generators.length === 0 }, function (t) { 148 | forEach(generators, function (genFn) { 149 | t.ok(isCallable(genFn), 'generator function ' + genFn + ' is callable'); 150 | }); 151 | t.end(); 152 | }); 153 | 154 | test('Arrow functions', { skip: arrows.length === 0 }, function (t) { 155 | forEach(arrows, function (arrowFn) { 156 | t.ok(isCallable(arrowFn), 'arrow function ' + arrowFn + ' is callable'); 157 | }); 158 | t.ok(isCallable(weirdlyCommentedArrowFn), 'weirdly commented arrow functions are callable'); 159 | t.end(); 160 | }); 161 | 162 | test('"Class" constructors', { 163 | skip: !classConstructor || !commentedClass || !commentedClassOneLine || !classAnonymous, todo: !hasDetectableClasses 164 | }, function (t) { 165 | if (!hasDetectableClasses) { 166 | t.comment('WARNING: This engine does not support detectable classes'); 167 | } 168 | t.notOk(isCallable(classConstructor), 'class constructors are not callable'); 169 | t.notOk(isCallable(commentedClass), 'class constructors with comments in the signature are not callable'); 170 | t.notOk(isCallable(commentedClassOneLine), 'one-line class constructors with comments in the signature are not callable'); 171 | t.notOk(isCallable(classAnonymous), 'anonymous class constructors are not callable'); 172 | t.notOk(isCallable(classAnonymousCommentedOneLine), 'anonymous one-line class constructors with comments in the signature are not callable'); 173 | t.end(); 174 | }); 175 | 176 | test('`async function`s', { skip: asyncs.length === 0 }, function (t) { 177 | forEach(asyncs, function (asyncFn) { 178 | t.ok(isCallable(asyncFn), '`async function` ' + asyncFn + ' is callable'); 179 | }); 180 | t.end(); 181 | }); 182 | 183 | test('proxies of functions', { skip: !proxy }, function (t) { 184 | t.equal(isCallable(proxy), true, 'proxies of functions are callable'); 185 | t.end(); 186 | }); 187 | 188 | test('throwing functions', function (t) { 189 | t.plan(1); 190 | 191 | var thrower = function (a) { return a.b; }; 192 | t.ok(isCallable(thrower), 'a function that throws is callable'); 193 | }); 194 | 195 | test('DOM', function (t) { 196 | /* eslint-env browser */ 197 | 198 | t.test('document.all', { skip: typeof document !== 'object' }, function (st) { 199 | st.notOk(isCallable(document), 'document is not callable'); 200 | 201 | var all = document.all; 202 | var isFF3 = !isIE68 && Object.prototype.toString(all) === Object.prototype.toString.call(document.all); // this test is true in IE 6-8 also 203 | var expected = false; 204 | if (!isFF3) { 205 | try { 206 | expected = document.all('') == null; // eslint-disable-line eqeqeq 207 | } catch (e) { /**/ } 208 | } 209 | st.equal(isCallable(document.all), expected, 'document.all is ' + (isFF3 ? 'not ' : '') + 'callable'); 210 | 211 | st.end(); 212 | }); 213 | 214 | forEach([ 215 | 'HTMLElement', 216 | 'HTMLAnchorElement' 217 | ], function (name) { 218 | var constructor = global[name]; 219 | 220 | t.test(name, { skip: !constructor }, function (st) { 221 | st.match(typeof constructor, /^(?:function|object)$/, name + ' is a function or object'); 222 | 223 | var callable = isCallable(constructor); 224 | st.equal(typeof callable, 'boolean'); 225 | 226 | if (callable) { 227 | st.doesNotThrow( 228 | function () { Function.prototype.toString.call(constructor); }, 229 | 'anything this library claims is callable should be accepted by Function toString' 230 | ); 231 | } else { 232 | st['throws']( 233 | function () { Function.prototype.toString.call(constructor); }, 234 | TypeError, 235 | 'anything this library claims is not callable should not be accepted by Function toString' 236 | ); 237 | } 238 | 239 | st.end(); 240 | }); 241 | }); 242 | 243 | t.end(); 244 | }); 245 | --------------------------------------------------------------------------------