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 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', 'error-only']
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
- ### 𝄃𝄃𝄂𝄂𝄀𝄁𝄃𝄂𝄂𝄃 `no-explicit-return`
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
- ### 𝄃𝄃𝄂𝄂𝄀𝄁𝄃𝄂𝄂𝄃 `require-error-subclass-name`
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
- ### 𝄃𝄃𝄂𝄂𝄀𝄁𝄃𝄂𝄂𝄃 `require-new` (auto-fixable)
144
+ ### `require-new` (auto-fixable)
141
145
 
142
- > Requires `new` when calling error constructors (and optionally other built-in constructors).
146
+ > Requires `new` when calling error constructors (and optionally other built-ins).
143
147
 
144
148
  #### Options
145
149
 
146
- - `'error-only'` (default) – only error constructors (`AggregateError`, `Error`, `EvalError`, `RangeError`, `ReferenceError`, `SyntaxError`, `TypeError`, `URIError`)
147
- - `'builtins'` – also includes `Array`, `BigInt`, `Boolean`, `Date`, `Function`, `Map`, `Number`, `Object`, `RegExp`, `Set`, `String`, `Symbol`, `WeakMap`, `WeakSet`
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
- // w/ builtins:
156
- const arr = Array(),
157
- m = Map()
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
- // w/ builtins:
167
- const arr = new Array(),
168
- m = new Map()
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 the convenience of calling built-ins without `new` (which is allowed for some of them).
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
  [![Linted by eslint-plugin-constructors](https://img.shields.io/badge/Linted_by-eslint--plugin--constructors-4b32c3?logo=eslint&logoColor=white&labelColor=464646&style=for-the-badge)](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.1.0",
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": "eslint --config demo/eslint.config.js demo/buggy-script.js",
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
- 'require-new': requireNew,
7
+ 'no-explicit-return': noExplicitReturn,
8
8
  'require-error-subclass-name': requireErrorSubclassName,
9
- 'no-explicit-return': noExplicitReturn
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/require-new': 'error',
17
+ 'constructors/no-explicit-return': 'error',
18
18
  'constructors/require-error-subclass-name': 'error',
19
- 'constructors/no-explicit-return': 'error'
19
+ 'constructors/require-new': 'error'
20
20
  }
21
21
  }
22
22
  }
@@ -1,9 +1,9 @@
1
- const reFuncNodeType = /^(?:Arrow)?Function(?:Declaration|Expression)$/
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 (reFuncNodeType.test(current.type))
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'
@@ -1,13 +1,20 @@
1
- const errors = new Set([
2
- 'AggregateError', 'Error', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError'])
1
+ import globals from 'globals'
3
2
 
4
- const builtins = new Set([
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: "error-only" (default) or "builtins".'
29
+ enum: ['errors', 'throwing', 'all', 'error-only', 'builtins'],
30
+ description: 'Constructor categories to check'
24
31
  }],
25
- defaultOptions: ['error-only'],
26
- messages: { requireNew: 'Use `new {{name}}()` instead of `{{name}}()`' }
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 mode = context.options[0] || 'error-only'
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
- const shouldReport = mode == 'error-only' ? isErrConstructor(calleeName)
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 ') }