Skip to content

Commit 8197574

Browse files
axetroysindresorhusfisker
authored
Add no-instanceof-builtin-object rule (#2523)
Co-authored-by: Sindre Sorhus <[email protected]> Co-authored-by: fisker <[email protected]>
1 parent 3b5faba commit 8197574

14 files changed

+2596
-17
lines changed
+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Disallow `instanceof` with built-in objects
2+
3+
💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs).
4+
5+
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
6+
7+
<!-- end auto-generated rule header -->
8+
<!-- Do not manually modify this header. Run: `npm run fix:eslint-docs` -->
9+
10+
Using `instanceof` to determine the type of an object has [limitations](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof#instanceof_and_multiple_realms).
11+
12+
Therefore, it is recommended to use a safer method, like `Object.prototype.toString.call(foo)` or the npm package [@sindresorhus/is](https://www.npmjs.com/package/@sindresorhus/is) to determine the type of an object.
13+
14+
## Examples
15+
16+
```js
17+
foo instanceof String; //
18+
typeof foo === 'string'; //
19+
```
20+
21+
```js
22+
foo instanceof Number; //
23+
typeof foo === 'number'; //
24+
```
25+
26+
```js
27+
foo instanceof Boolean; //
28+
typeof foo === 'boolean'; //
29+
```
30+
31+
```js
32+
foo instanceof BigInt; //
33+
typeof foo === 'bigint'; //
34+
```
35+
36+
```js
37+
foo instanceof Symbol; //
38+
typeof foo === 'symbol'; //
39+
```
40+
41+
```js
42+
foo instanceof Array; //
43+
Array.isArray(foo); //
44+
```
45+
46+
```js
47+
foo instanceof Function; //
48+
typeof foo === 'function'; //
49+
```
50+
51+
```js
52+
foo instanceof Object; //
53+
Object.prototype.toString.call(foo) === '[object Object]'; //
54+
```
55+
56+
```js
57+
import is from '@sindresorhus/is';
58+
59+
foo instanceof Map; //
60+
is(foo) === 'Map'; //
61+
```
62+
63+
## Options
64+
65+
### strategy
66+
67+
Type: `'loose' | 'strict'`\
68+
Default: `'loose'`
69+
70+
The matching strategy:
71+
72+
- `'loose'` - Matches the primitive type (`string`, `number`, `boolean`, `bigint`, `symbol`) constructors, `Function`, and `Array`.
73+
- `'strict'` - Matches all built-in constructors.
74+
75+
```js
76+
"unicorn/no-instanceof-builtin-object": [
77+
"error",
78+
{
79+
"strategy": "strict"
80+
}
81+
]
82+
```
83+
84+
### include
85+
86+
Type: `string[]`\
87+
Default: `[]`
88+
89+
Specify the constructors that should be validated.
90+
91+
```js
92+
"unicorn/no-instanceof-builtin-object": [
93+
"error",
94+
{
95+
"include": [
96+
"WebWorker",
97+
"HTMLElement"
98+
]
99+
}
100+
]
101+
```
102+
103+
### exclude
104+
105+
Type: `string[]`\
106+
Default: `[]`
107+
108+
Specifies the constructors that should be excluded, with this rule taking precedence over others.
109+
110+
```js
111+
"unicorn/no-instanceof-builtin-object": [
112+
"error",
113+
{
114+
"exclude": [
115+
"String",
116+
"Number"
117+
]
118+
}
119+
]
120+
```
121+
122+
### useErrorIsError
123+
124+
Type: `boolean`\
125+
Default: `false`
126+
127+
Specifies using [`Error.isError()`](https://github.com/tc39/proposal-is-error) to determine whether it is an error object.
128+
129+
```js
130+
"unicorn/no-instanceof-builtin-object": [
131+
"error",
132+
{
133+
"strategy": "strict",
134+
"useErrorIsError": true
135+
}
136+
]
137+
```
138+
139+
This option will be removed at some point in the future.

readme.md

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export default [
8686
| [no-for-loop](docs/rules/no-for-loop.md) | Do not use a `for` loop that can be replaced with a `for-of` loop. || 🔧 | 💡 |
8787
| [no-hex-escape](docs/rules/no-hex-escape.md) | Enforce the use of Unicode escapes instead of hexadecimal escapes. || 🔧 | |
8888
| [no-instanceof-array](docs/rules/no-instanceof-array.md) | Require `Array.isArray()` instead of `instanceof Array`. || 🔧 | |
89+
| [no-instanceof-builtin-object](docs/rules/no-instanceof-builtin-object.md) | Disallow `instanceof` with built-in objects || 🔧 | |
8990
| [no-invalid-fetch-options](docs/rules/no-invalid-fetch-options.md) | Disallow invalid options in `fetch()` and `new Request()`. || | |
9091
| [no-invalid-remove-event-listener](docs/rules/no-invalid-remove-event-listener.md) | Prevent calling `EventTarget#removeEventListener()` with the result of an expression. || | |
9192
| [no-keyword-prefix](docs/rules/no-keyword-prefix.md) | Disallow identifiers starting with `new` or `class`. | | | |

rules/catch-error-name.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {isRegExp} from 'node:util/types';
12
import {findVariable} from '@eslint-community/eslint-utils';
23
import avoidCapture from './utils/avoid-capture.js';
34
import {renameVariable} from './fix/index.js';
@@ -40,7 +41,7 @@ const create = context => {
4041
};
4142
const {name: expectedName} = options;
4243
const ignore = options.ignore.map(
43-
pattern => pattern instanceof RegExp ? pattern : new RegExp(pattern, 'u'),
44+
pattern => isRegExp(pattern) ? pattern : new RegExp(pattern, 'u'),
4445
);
4546
const isNameAllowed = name =>
4647
name === expectedName

rules/error-message.js

+1-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {getStaticValue} from '@eslint-community/eslint-utils';
22
import isShadowed from './utils/is-shadowed.js';
33
import {isCallOrNewExpression} from './ast/index.js';
4+
import builtinErrors from './shared/builtin-errors.js';
45

56
const MESSAGE_ID_MISSING_MESSAGE = 'missing-message';
67
const MESSAGE_ID_EMPTY_MESSAGE = 'message-is-empty-string';
@@ -11,19 +12,6 @@ const messages = {
1112
[MESSAGE_ID_NOT_STRING]: 'Error message should be a string.',
1213
};
1314

14-
const builtinErrors = [
15-
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
16-
'Error',
17-
'EvalError',
18-
'RangeError',
19-
'ReferenceError',
20-
'SyntaxError',
21-
'TypeError',
22-
'URIError',
23-
'InternalError',
24-
'AggregateError',
25-
];
26-
2715
/** @param {import('eslint').Rule.RuleContext} context */
2816
const create = context => {
2917
context.on(['CallExpression', 'NewExpression'], expression => {

rules/expiring-todo-comments.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import path from 'node:path';
2+
import {isRegExp} from 'node:util/types';
23
import {readPackageUpSync} from 'read-package-up';
34
import semver from 'semver';
45
import * as ci from 'ci-info';
@@ -286,7 +287,7 @@ const create = context => {
286287
};
287288

288289
const ignoreRegexes = options.ignore.map(
289-
pattern => pattern instanceof RegExp ? pattern : new RegExp(pattern, 'u'),
290+
pattern => isRegExp(pattern) ? pattern : new RegExp(pattern, 'u'),
290291
);
291292

292293
const dirname = path.dirname(context.filename);

rules/filename-case.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import path from 'node:path';
2+
import {isRegExp} from 'node:util/types';
23
import {
34
camelCase,
45
kebabCase,
@@ -164,7 +165,7 @@ const create = context => {
164165
const options = context.options[0] || {};
165166
const chosenCases = getChosenCases(options);
166167
const ignore = (options.ignore || []).map(item => {
167-
if (item instanceof RegExp) {
168+
if (isRegExp(item)) {
168169
return item;
169170
}
170171

rules/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import noEmptyFile from './no-empty-file.js';
3131
import noForLoop from './no-for-loop.js';
3232
import noHexEscape from './no-hex-escape.js';
3333
import noInstanceofArray from './no-instanceof-array.js';
34+
import noInstanceofBuiltinObject from './no-instanceof-builtin-object.js';
3435
import noInvalidFetchOptions from './no-invalid-fetch-options.js';
3536
import noInvalidRemoveEventListener from './no-invalid-remove-event-listener.js';
3637
import noKeywordPrefix from './no-keyword-prefix.js';
@@ -156,6 +157,7 @@ const rules = {
156157
'no-for-loop': createRule(noForLoop, 'no-for-loop'),
157158
'no-hex-escape': createRule(noHexEscape, 'no-hex-escape'),
158159
'no-instanceof-array': createRule(noInstanceofArray, 'no-instanceof-array'),
160+
'no-instanceof-builtin-object': createRule(noInstanceofBuiltinObject, 'no-instanceof-builtin-object'),
159161
'no-invalid-fetch-options': createRule(noInvalidFetchOptions, 'no-invalid-fetch-options'),
160162
'no-invalid-remove-event-listener': createRule(noInvalidRemoveEventListener, 'no-invalid-remove-event-listener'),
161163
'no-keyword-prefix': createRule(noKeywordPrefix, 'no-keyword-prefix'),

0 commit comments

Comments
 (0)