├── .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 |
--------------------------------------------------------------------------------