eslint-plugin-constructors 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -24
- package/package.json +5 -3
- package/src/index.js +6 -6
- package/src/rules/no-explicit-return.js +2 -2
- package/src/rules/require-error-subclass-name.js +5 -5
- package/src/rules/require-new.js +39 -13
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ ESLint rules to enforce best practices for constructor usage.
|
|
|
10
10
|
|
|
11
11
|
<img src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-constructors/6eeba7b2cb/assets/images/screenshots/vs-code/squiggles.png">
|
|
12
12
|
|
|
13
|
+
---
|
|
14
|
+
|
|
13
15
|
## Installation
|
|
14
16
|
|
|
15
17
|
From your project root:
|
|
@@ -18,6 +20,8 @@ From your project root:
|
|
|
18
20
|
npm i -D eslint-plugin-constructors
|
|
19
21
|
```
|
|
20
22
|
|
|
23
|
+
---
|
|
24
|
+
|
|
21
25
|
## Usage
|
|
22
26
|
|
|
23
27
|
Add `constructors` to your flat config:
|
|
@@ -32,7 +36,7 @@ export default [
|
|
|
32
36
|
rules: {
|
|
33
37
|
'constructors/no-explicit-return': 'error',
|
|
34
38
|
'constructors/require-error-subclass-name': 'error',
|
|
35
|
-
'constructors/require-new': ['error', '
|
|
39
|
+
'constructors/require-new': ['error', 'errors']
|
|
36
40
|
}
|
|
37
41
|
}
|
|
38
42
|
]
|
|
@@ -53,19 +57,19 @@ export default [
|
|
|
53
57
|
]
|
|
54
58
|
```
|
|
55
59
|
|
|
60
|
+
---
|
|
61
|
+
|
|
56
62
|
## Supported rules
|
|
57
63
|
|
|
58
|
-
<!-- eslint-disable markdown/no-missing-link-fragments -->
|
|
59
64
|
| ID | Description | Auto-fixable | Recommended |
|
|
60
65
|
| --- | --- | :-: | :-: |
|
|
61
66
|
| [`no-explicit-return`](#no-explicit-return) | Disallows explicit `return` statements in class constructors | ❌ | ✅ |
|
|
62
67
|
| [`require-error-subclass-name`](#require-error-subclass-name) | Requires classes extending `Error` to have names ending in `Error` | ❌ | ✅ |
|
|
63
68
|
| [`require-new`](#require-new-auto-fixable) | Requires `new` when calling error constructors | ✅ | ✅ |
|
|
64
|
-
<!-- eslint-enable markdown/no-missing-link-fragments -->
|
|
65
69
|
|
|
66
70
|
---
|
|
67
71
|
|
|
68
|
-
###
|
|
72
|
+
### `no-explicit-return`
|
|
69
73
|
|
|
70
74
|
> Disallows explicit `return` statements inside class constructors.
|
|
71
75
|
|
|
@@ -107,7 +111,7 @@ If you need to return a different object from a constructor.
|
|
|
107
111
|
|
|
108
112
|
---
|
|
109
113
|
|
|
110
|
-
###
|
|
114
|
+
### `require-error-subclass-name`
|
|
111
115
|
|
|
112
116
|
> Requires classes extending `Error` have a name ending in `Error`.
|
|
113
117
|
|
|
@@ -137,40 +141,44 @@ If you don't follow the convention of suffixing custom error classes w/ `Error`.
|
|
|
137
141
|
|
|
138
142
|
---
|
|
139
143
|
|
|
140
|
-
###
|
|
144
|
+
### `require-new` (auto-fixable)
|
|
141
145
|
|
|
142
|
-
> Requires `new` when calling error constructors (and optionally other built-
|
|
146
|
+
> Requires `new` when calling error constructors (and optionally other built-ins).
|
|
143
147
|
|
|
144
148
|
#### Options
|
|
145
149
|
|
|
146
|
-
- `'
|
|
147
|
-
- `'
|
|
150
|
+
- `'errors'` (default) — require for error constructors.
|
|
151
|
+
- `'throwing'` — require for errors + constructors that throw a `TypeError` when called without `new`.
|
|
152
|
+
- `'all'` — require for errors + throwing constructors + constructors where `new` is optional.
|
|
153
|
+
|
|
154
|
+
Excluded: `Boolean`, `Number`, `String` (calling w/ `new` returns wrapper objects), and `Symbol`, `BigInt` (cannot be called w/ `new`).
|
|
148
155
|
|
|
149
156
|
#### Incorrect code
|
|
150
157
|
|
|
151
158
|
```js
|
|
159
|
+
// 'errors' (default)
|
|
152
160
|
throw Error('boom')
|
|
153
|
-
throw TypeError('bad')
|
|
154
161
|
|
|
155
|
-
//
|
|
156
|
-
const
|
|
157
|
-
|
|
162
|
+
// 'throwing'
|
|
163
|
+
const m = Map()
|
|
164
|
+
|
|
165
|
+
// 'all'
|
|
166
|
+
const arr = Array()
|
|
158
167
|
```
|
|
159
168
|
|
|
160
169
|
#### Correct code
|
|
161
170
|
|
|
162
171
|
```js
|
|
163
172
|
throw new Error('boom')
|
|
164
|
-
throw new TypeError('bad')
|
|
165
173
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
174
|
+
const m = new Map()
|
|
175
|
+
|
|
176
|
+
const arr = new Array()
|
|
169
177
|
```
|
|
170
178
|
|
|
171
179
|
#### When not to use it
|
|
172
180
|
|
|
173
|
-
If you prefer
|
|
181
|
+
If you prefer calling built-ins w/o `new` (which the runtime allows for some of them) and don't want to enforce it for consistency.
|
|
174
182
|
|
|
175
183
|
<div align="center">
|
|
176
184
|
|
|
@@ -198,17 +206,15 @@ If you prefer the convenience of calling built-ins without `new` (which is allow
|
|
|
198
206
|
[](https://codeberg.org/adamlui/eslint-plugin-constructors/#readme)
|
|
199
207
|
```
|
|
200
208
|
|
|
209
|
+
---
|
|
210
|
+
|
|
201
211
|
## License
|
|
202
212
|
|
|
203
213
|
[<img height=62 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-constructors/af5fe76586/assets/images/logos/mit-license/red-pixelated.png">][epc-license]
|
|
204
214
|
|
|
205
215
|
<details>
|
|
206
216
|
|
|
207
|
-
<summary>
|
|
208
|
-
|
|
209
|
-
Copyright © 2026 [Adam Lui][adamlui-cb] under the [MIT license][epc-license].
|
|
210
|
-
|
|
211
|
-
</summary><br>
|
|
217
|
+
<summary>Copyright © 2026 <a href="https://codeberg.org/adamlui">Adam Lui</a> under the <a href="https://codeberg.org/adamlui/eslint-plugin-constructors/src/branch/main/docs/LICENSE.md">MIT license</a>.</summary><br>
|
|
212
218
|
|
|
213
219
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
214
220
|
|
|
@@ -218,6 +224,8 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|
|
218
224
|
|
|
219
225
|
</details>
|
|
220
226
|
|
|
227
|
+
---
|
|
228
|
+
|
|
221
229
|
## Related
|
|
222
230
|
|
|
223
231
|
<!-- eslint-plugin-greasemonkey -->
|
|
@@ -251,7 +259,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|
|
251
259
|
|
|
252
260
|
<!-- Links -->
|
|
253
261
|
|
|
254
|
-
[adamlui-cb]: https://codeberg.org/adamlui
|
|
255
262
|
[adamlui-npmx]: https://npmx.dev/org/adamlui
|
|
256
263
|
[epc-bugs]: https://codeberg.org/adamlui/eslint-plugin-constructors/issues/new
|
|
257
264
|
[epc-license]: https://codeberg.org/adamlui/eslint-plugin-constructors/src/branch/main/docs/LICENSE.md
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-constructors",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "ESLint rules for constructor usage",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"lint:fix": "eslint . --fix --cache && yamllint .",
|
|
41
41
|
"lint:fix-all": "eslint . --fix && yamllint .",
|
|
42
42
|
"test": "node tests/run.js",
|
|
43
|
-
"demo": "
|
|
43
|
+
"demo": "node demo/run.js",
|
|
44
44
|
"bump:patch": "bash utils/bump.sh patch",
|
|
45
45
|
"bump:minor": "bash utils/bump.sh minor",
|
|
46
46
|
"bump:feat": "npm run bump:minor",
|
|
@@ -59,6 +59,9 @@
|
|
|
59
59
|
"eslintplugin",
|
|
60
60
|
"security"
|
|
61
61
|
],
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"globals": "^17.12.0"
|
|
64
|
+
},
|
|
62
65
|
"peerDependencies": {
|
|
63
66
|
"eslint": ">=9.0.0"
|
|
64
67
|
},
|
|
@@ -77,7 +80,6 @@
|
|
|
77
80
|
"eslint-plugin-promise": "^7.3.0",
|
|
78
81
|
"eslint-plugin-regexp": "^3.3.0",
|
|
79
82
|
"eslint-plugin-security": "^4.0.1",
|
|
80
|
-
"globals": "^17.12.0",
|
|
81
83
|
"husky": "^9.1.7",
|
|
82
84
|
"yamllint-js": "^0.2.4"
|
|
83
85
|
}
|
package/src/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import requireNew from './rules/require-new.js'
|
|
2
|
-
import requireErrorSubclassName from './rules/require-error-subclass-name.js'
|
|
3
1
|
import noExplicitReturn from './rules/no-explicit-return.js'
|
|
2
|
+
import requireErrorSubclassName from './rules/require-error-subclass-name.js'
|
|
3
|
+
import requireNew from './rules/require-new.js'
|
|
4
4
|
|
|
5
5
|
const plugin = {
|
|
6
6
|
rules: {
|
|
7
|
-
'
|
|
7
|
+
'no-explicit-return': noExplicitReturn,
|
|
8
8
|
'require-error-subclass-name': requireErrorSubclassName,
|
|
9
|
-
'
|
|
9
|
+
'require-new': requireNew
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -14,9 +14,9 @@ plugin.configs = {
|
|
|
14
14
|
recommended: {
|
|
15
15
|
plugins: { constructors: plugin },
|
|
16
16
|
rules: {
|
|
17
|
-
'constructors/
|
|
17
|
+
'constructors/no-explicit-return': 'error',
|
|
18
18
|
'constructors/require-error-subclass-name': 'error',
|
|
19
|
-
'constructors/
|
|
19
|
+
'constructors/require-new': 'error'
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const
|
|
1
|
+
const re = { funcNodeType: /^(?:Arrow)?Function(?:Declaration|Expression)$/ }
|
|
2
2
|
|
|
3
3
|
function isInsideConstructorFunc(node) {
|
|
4
4
|
let current = node.parent
|
|
5
5
|
while (current) {
|
|
6
|
-
if (
|
|
6
|
+
if (re.funcNodeType.test(current.type))
|
|
7
7
|
return ( // whether this func is constructor's val
|
|
8
8
|
current.parent?.type == 'MethodDefinition'
|
|
9
9
|
&& current.parent.kind == 'constructor'
|
|
@@ -11,16 +11,16 @@ export default {
|
|
|
11
11
|
},
|
|
12
12
|
|
|
13
13
|
create(context) {
|
|
14
|
-
function checkName(name, node) {
|
|
15
|
-
if (!name || name.endsWith('Error')) return
|
|
16
|
-
context.report({ node, messageId: 'endWithError' })
|
|
17
|
-
}
|
|
18
|
-
|
|
19
14
|
function checkClassNode(node) {
|
|
20
15
|
if (!node.id || !node.superClass?.name?.endsWith('Error')) return
|
|
21
16
|
checkName(node.id.name, node.id)
|
|
22
17
|
}
|
|
23
18
|
|
|
19
|
+
function checkName(name, node) {
|
|
20
|
+
if (!name || name.endsWith('Error')) return
|
|
21
|
+
context.report({ node, messageId: 'endWithError' })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
24
|
function checkVariableDeclarator(node) {
|
|
25
25
|
const classExpr = node.init
|
|
26
26
|
if (classExpr?.type != 'ClassExpression'
|
package/src/rules/require-new.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
'AggregateError', 'Error', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError'])
|
|
1
|
+
import globals from 'globals'
|
|
3
2
|
|
|
4
|
-
const
|
|
3
|
+
const errorConstructors = new Set(Object.keys(globals.builtin).filter(name => name.endsWith('Error')))
|
|
4
|
+
|
|
5
|
+
const throwingConstructors = new Set([
|
|
6
|
+
'ArrayBuffer', 'BigInt64Array', 'BigUint64Array', 'DataView', 'FinalizationRegistry', 'Float32Array',
|
|
7
|
+
'Float64Array', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Promise', 'Proxy', 'Set', 'SharedArrayBuffer',
|
|
8
|
+
'Uint8Array', 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap', 'WeakRef', 'WeakSet'
|
|
9
|
+
])
|
|
10
|
+
|
|
11
|
+
const lenientConstructors = new Set(['Array', 'Date', 'Function', 'Object', 'RegExp'])
|
|
12
|
+
|
|
13
|
+
const legacyBuiltins = new Set([
|
|
5
14
|
'Array', 'BigInt', 'Boolean', 'Date', 'Function', 'Map', 'Number', 'Object', 'RegExp', 'Set', 'String', 'Symbol',
|
|
6
15
|
'WeakMap', 'WeakSet'
|
|
7
16
|
])
|
|
8
17
|
|
|
9
|
-
function isErrConstructor(name) { return errors.has(name) }
|
|
10
|
-
|
|
11
18
|
export default {
|
|
12
19
|
meta: {
|
|
13
20
|
type: 'problem',
|
|
@@ -19,22 +26,41 @@ export default {
|
|
|
19
26
|
fixable: 'code',
|
|
20
27
|
schema: [{
|
|
21
28
|
type: 'string',
|
|
22
|
-
enum: ['error-only', 'builtins'],
|
|
23
|
-
description: 'Constructor categories to check
|
|
29
|
+
enum: ['errors', 'throwing', 'all', 'error-only', 'builtins'],
|
|
30
|
+
description: 'Constructor categories to check'
|
|
24
31
|
}],
|
|
25
|
-
defaultOptions: ['
|
|
26
|
-
messages: {
|
|
32
|
+
defaultOptions: ['errors'],
|
|
33
|
+
messages: {
|
|
34
|
+
requireNew: 'Use `new {{name}}()` instead of `{{name}}()`',
|
|
35
|
+
deprecatedMode: `'{{old}}' is deprecated. Use '{{new}}' instead.`
|
|
36
|
+
}
|
|
27
37
|
},
|
|
28
38
|
|
|
29
39
|
create(context) {
|
|
30
|
-
const
|
|
40
|
+
const rawMode = context.options[0] || 'errors'
|
|
41
|
+
let mode = rawMode, deprecation
|
|
42
|
+
if (rawMode == 'error-only') { mode = 'errors' ; deprecation = { old: 'error-only', new: 'errors' }}
|
|
43
|
+
else if (rawMode == 'builtins') mode = 'legacy-builtins'
|
|
44
|
+
|
|
45
|
+
function shouldReportName(name) {
|
|
46
|
+
return mode == 'errors' ? errorConstructors.has(name)
|
|
47
|
+
: mode == 'throwing' ? errorConstructors.has(name) || throwingConstructors.has(name)
|
|
48
|
+
: mode == 'all' ? errorConstructors.has(name) || throwingConstructors.has(name)
|
|
49
|
+
|| lenientConstructors.has(name)
|
|
50
|
+
: mode == 'legacy-builtins' ? errorConstructors.has(name) || legacyBuiltins.has(name)
|
|
51
|
+
: false
|
|
52
|
+
}
|
|
53
|
+
|
|
31
54
|
return {
|
|
55
|
+
Program(node) {
|
|
56
|
+
if (deprecation)
|
|
57
|
+
context.report({
|
|
58
|
+
node, messageId: 'deprecatedMode', data: { old: deprecation.old, new: deprecation.new }})
|
|
59
|
+
},
|
|
32
60
|
CallExpression(node) {
|
|
33
61
|
if (node.new) return
|
|
34
62
|
const calleeName = node.callee.name ; if (!calleeName) return
|
|
35
|
-
|
|
36
|
-
: isErrConstructor(calleeName) || builtins.has(calleeName)
|
|
37
|
-
if (shouldReport)
|
|
63
|
+
if (shouldReportName(calleeName))
|
|
38
64
|
context.report({
|
|
39
65
|
node, messageId: 'requireNew', data: { name: calleeName },
|
|
40
66
|
fix(fixer) { return fixer.insertTextBefore(node, 'new ') }
|