eslint-plugin-constructors 1.1.1 → 1.2.1

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,7 +20,14 @@ From your project root:
18
20
  npm i -D eslint-plugin-constructors
19
21
  ```
20
22
 
21
- ## Usage
23
+ ---
24
+
25
+ <h2 id="usage">
26
+ Usage
27
+ <a title="Copy section URL" target="_blank"
28
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-constructors%2F%23usage">
29
+ <img height=17 width=22 alt="[Copy section URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="bottom"></a>
30
+ </h2>
22
31
 
23
32
  Add `constructors` to your flat config:
24
33
 
@@ -32,7 +41,7 @@ export default [
32
41
  rules: {
33
42
  'constructors/no-explicit-return': 'error',
34
43
  'constructors/require-error-subclass-name': 'error',
35
- 'constructors/require-new': ['error', 'error-only']
44
+ 'constructors/require-new': ['error', 'errors']
36
45
  }
37
46
  }
38
47
  ]
@@ -53,44 +62,65 @@ export default [
53
62
  ]
54
63
  ```
55
64
 
56
- ## Supported rules
65
+ ---
66
+
67
+ <h2 id="supported-rules">
68
+ Supported rules
69
+ <a title="Copy section URL" target="_blank"
70
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-constructors%2F%23supported-rules">
71
+ <img height=17 width=22 alt="[Copy section URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="bottom"></a>
72
+ </h2>
57
73
 
58
74
  | ID | Description | Auto-fixable | Recommended |
59
75
  | --- | --- | :-: | :-: |
60
76
  | [`no-explicit-return`](#no-explicit-return) | Disallows explicit `return` statements in class constructors | ❌ | ✅ |
61
77
  | [`require-error-subclass-name`](#require-error-subclass-name) | Requires classes extending `Error` to have names ending in `Error` | ❌ | ✅ |
62
- | [`require-new`](#require-new-auto-fixable) | Requires `new` when calling error constructors | ✅ | ✅ |
78
+ | [`require-new`](#require-new) | Requires `new` when calling error constructors | ✅ | ✅ |
63
79
 
64
80
  ---
65
81
 
66
- ### `no-explicit-return`
82
+ <h3 id="no-explicit-return">
83
+ <code>no-explicit-return</code>
84
+ <a title="Included in recommended config" href="#usage">
85
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
86
+ <a title="Copy rule URL" target="_blank"
87
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-constructors%2F%23no-explicit-return">
88
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
89
+ </h3>
67
90
 
68
91
  > Disallows explicit `return` statements inside class constructors.
69
92
 
70
93
  #### Incorrect code
71
94
 
72
- ```js
73
- class A {
74
- constructor() {
75
- return
76
- }
77
- }
78
-
79
- class B {
80
- constructor() {
81
- return this
82
- }
83
- }
95
+ ```diff
96
+ class A {
97
+ constructor() {
98
+ ! return
99
+ }
100
+ }
101
+
102
+ class B {
103
+ constructor() {
104
+ ! return this
105
+ }
106
+ }
84
107
  ```
85
108
 
86
109
  #### Correct code
87
110
 
88
- ```js
89
- class A {
90
- constructor() {
91
- this.x = 1
92
- }
93
- }
111
+ ```diff
112
+ class A {
113
+ constructor() {
114
+ - return
115
+ + this.x = 1
116
+ }
117
+
118
+ class B {
119
+ constructor() {
120
+ - return this
121
+ + this.x = 1
122
+ }
123
+ }
94
124
  ```
95
125
 
96
126
  #### When not to use it
@@ -105,22 +135,31 @@ If you need to return a different object from a constructor.
105
135
 
106
136
  ---
107
137
 
108
- ### `require-error-subclass-name`
138
+ <h3 id="require-error-subclass-name">
139
+ <code>require-error-subclass-name</code>
140
+ <a title="Included in recommended config" href="#usage">
141
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
142
+ <a title="Copy rule URL" target="_blank"
143
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-constructors%2F%23require-error-subclass-name">
144
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
145
+ </h3>
109
146
 
110
147
  > Requires classes extending `Error` have a name ending in `Error`.
111
148
 
112
149
  #### Incorrect code
113
150
 
114
- ```js
115
- class My extends Error {}
116
- const Custom = class extends Error {}
151
+ ```diff
152
+ ! class My extends Error {}
153
+ ! const Custom = class extends Error {}
117
154
  ```
118
155
 
119
156
  #### Correct code
120
157
 
121
- ```js
122
- class MyError extends Error {}
123
- const CustomError = class extends Error {}
158
+ ```diff
159
+ - class My extends Error {}
160
+ - const Custom = class extends Error {}
161
+ + class MyError extends Error {}
162
+ + const CustomError = class extends Error {}
124
163
  ```
125
164
 
126
165
  #### When not to use it
@@ -135,40 +174,59 @@ If you don't follow the convention of suffixing custom error classes w/ `Error`.
135
174
 
136
175
  ---
137
176
 
138
- ### `require-new` (auto-fixable)
177
+ <h3 id="require-new">
178
+ <code>require-new</code>
179
+ <a title="Included in recommended config" href="#usage">
180
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
181
+ <a title="Accepts eslint --fix" target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface#--fix">
182
+ <img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-e8357a?style=flat-square" align="middle"></a>
183
+ <a title="Copy rule URL" target="_blank"
184
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-constructors%2F%23require-new">
185
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
186
+ </h3>
139
187
 
140
- > Requires `new` when calling error constructors (and optionally other built-in constructors).
188
+ > Requires `new` when calling error constructors (and optionally other built-ins).
141
189
 
142
190
  #### Options
143
191
 
144
- - `'error-only'` (default) – only error constructors (`AggregateError`, `Error`, `EvalError`, `RangeError`, `ReferenceError`, `SyntaxError`, `TypeError`, `URIError`)
145
- - `'builtins'` – also includes `Array`, `BigInt`, `Boolean`, `Date`, `Function`, `Map`, `Number`, `Object`, `RegExp`, `Set`, `String`, `Symbol`, `WeakMap`, `WeakSet`
192
+ - `'errors'` (default) — require for error constructors.
193
+ - `'throwing'` — require for errors + constructors that throw a `TypeError` when called without `new`.
194
+ - `'all'` — require for errors + throwing constructors + constructors where `new` is optional.
195
+
196
+ Excluded: `Boolean`, `Number`, `String` (calling w/ `new` returns wrapper objects), and `Symbol`, `BigInt` (cannot be called w/ `new`).
146
197
 
147
198
  #### Incorrect code
148
199
 
149
- ```js
150
- throw Error('boom')
151
- throw TypeError('bad')
200
+ ```diff
201
+ /* eslint constructors/require-new: ['error', 'errors'] */
202
+ ! throw Error('boom')
152
203
 
153
- // w/ builtins:
154
- const arr = Array(),
155
- m = Map()
204
+ /* eslint constructors/require-new: ['error', 'throwing'] */
205
+ ! const m = Map()
206
+
207
+ /* eslint constructors/require-new: ['error', 'all'] */
208
+ ! const arr = Array()
156
209
  ```
157
210
 
158
211
  #### Correct code
159
212
 
160
- ```js
161
- throw new Error('boom')
162
- throw new TypeError('bad')
213
+ ```diff
214
+ /* eslint constructors/require-new: ['error', 'errors'] */
215
+ - throw Error('boom')
216
+ + throw new Error('boom')
217
+
218
+ /* eslint constructors/require-new: ['error', 'throwing'] */
219
+ - const m = Map()
220
+ + const m = new Map()
163
221
 
164
- // w/ builtins:
165
- const arr = new Array(),
166
- m = new Map()
222
+ /* eslint constructors/require-new: ['error', 'all'] */
223
+ - const arr = Array()
224
+ + const arr = new Array()
167
225
  ```
168
226
 
169
227
  #### When not to use it
170
228
 
171
- If you prefer the convenience of calling built-ins without `new` (which is allowed for some of them).
229
+ 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.
172
230
 
173
231
  <div align="center">
174
232
 
@@ -178,35 +236,33 @@ If you prefer the convenience of calling built-ins without `new` (which is allow
178
236
 
179
237
  ---
180
238
 
181
- ## Shield
239
+ ## Shield embed
182
240
 
183
241
  <a href="https://codeberg.org/adamlui/eslint-plugin-constructors/#readme">
184
- <img height=32 alt="[Linted by eslint-plugin-constructors]" src="https://img.shields.io/badge/Linted_by-eslint--plugin--constructors-4b32c3?logo=eslint&logoColor=white&labelColor=464646&style=for-the-badge"></a>
242
+ <img height=32 alt="[Linted by eslint-plugin-constructors]" src="https://img.shields.io/badge/Linted_by-eslint--plugin--constructors-black?logo=eslint&logoColor=white&labelColor=4B32C3&style=for-the-badge"></a>
185
243
 
186
244
  #### HTML
187
245
 
188
246
  ```html
189
247
  <a href="https://codeberg.org/adamlui/eslint-plugin-constructors/#readme">
190
- <img height=32 alt="[Linted by eslint-plugin-constructors]" src="https://img.shields.io/badge/Linted_by-eslint--plugin--constructors-4b32c3?logo=eslint&logoColor=white&labelColor=464646&style=for-the-badge"></a>
248
+ <img height=32 alt="[Linted by eslint-plugin-constructors]" src="https://img.shields.io/badge/Linted_by-eslint--plugin--constructors-black?logo=eslint&logoColor=white&labelColor=4B32C3&style=for-the-badge"></a>
191
249
  ```
192
250
 
193
251
  #### Markdown
194
252
 
195
253
  ```md
196
- [![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)
254
+ [![Linted by eslint-plugin-constructors](https://img.shields.io/badge/Linted_by-eslint--plugin--constructors-black?logo=eslint&logoColor=white&labelColor=4B32C3&style=for-the-badge)](https://codeberg.org/adamlui/eslint-plugin-constructors/#readme)
197
255
  ```
198
256
 
257
+ ---
258
+
199
259
  ## License
200
260
 
201
261
  [<img height=62 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-constructors/af5fe76586/assets/images/logos/mit-license/red-pixelated.png">][epc-license]
202
262
 
203
263
  <details>
204
264
 
205
- <summary>
206
-
207
- Copyright © 2026 [Adam Lui][adamlui-cb] under the [MIT license][epc-license].
208
-
209
- </summary><br>
265
+ <summary>Copyright © 2026 <a href="https://codeberg.org/adamlui"><b>Adam Lui</b></a> under the <a href="https://codeberg.org/adamlui/eslint-plugin-constructors/src/branch/main/docs/LICENSE.md"><b>MIT license</b></a>.</summary><br>
210
266
 
211
267
  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:
212
268
 
@@ -216,6 +272,8 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
216
272
 
217
273
  </details>
218
274
 
275
+ ---
276
+
219
277
  ## Related
220
278
 
221
279
  <!-- eslint-plugin-greasemonkey -->
@@ -249,7 +307,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
249
307
 
250
308
  <!-- Links -->
251
309
 
252
- [adamlui-cb]: https://codeberg.org/adamlui
253
310
  [adamlui-npmx]: https://npmx.dev/org/adamlui
254
311
  [epc-bugs]: https://codeberg.org/adamlui/eslint-plugin-constructors/issues/new
255
312
  [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.1",
3
+ "version": "1.2.1",
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
  }
@@ -0,0 +1,3 @@
1
+ export const options = { 'require-new': { 'error-only': 'errors' }} // 2026-9-13
2
+
3
+ export default { options }
package/src/index.js CHANGED
@@ -1,22 +1,26 @@
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
 
13
+ const docsURL = 'https://codeberg.org/adamlui/eslint-plugin-constructors/#'
14
+ for (const [ruleId, rule] of Object.entries(plugin.rules))
15
+ rule.meta.docs.url = `${docsURL}${ruleId}${ rule.meta.fixable ? '-auto-fixable' : '' }`
16
+
13
17
  plugin.configs = {
14
18
  recommended: {
15
19
  plugins: { constructors: plugin },
16
20
  rules: {
17
- 'constructors/require-new': 'error',
21
+ 'constructors/no-explicit-return': 'error',
18
22
  'constructors/require-error-subclass-name': 'error',
19
- 'constructors/no-explicit-return': 'error'
23
+ 'constructors/require-new': 'error'
20
24
  }
21
25
  }
22
26
  }
@@ -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,21 @@
1
- const errors = new Set([
2
- 'AggregateError', 'Error', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError'])
1
+ import deprecations from '../data/deprecations.js'
2
+ import globals from 'globals'
3
3
 
4
- const builtins = new Set([
4
+ const errorConstructors = new Set(Object.keys(globals.builtin).filter(name => name.endsWith('Error')))
5
+
6
+ const throwingConstructors = new Set([
7
+ 'ArrayBuffer', 'BigInt64Array', 'BigUint64Array', 'DataView', 'FinalizationRegistry', 'Float32Array',
8
+ 'Float64Array', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Promise', 'Proxy', 'Set', 'SharedArrayBuffer',
9
+ 'Uint8Array', 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap', 'WeakRef', 'WeakSet'
10
+ ])
11
+
12
+ const lenientConstructors = new Set(['Array', 'Date', 'Function', 'Object', 'RegExp'])
13
+
14
+ const legacyBuiltins = new Set([
5
15
  'Array', 'BigInt', 'Boolean', 'Date', 'Function', 'Map', 'Number', 'Object', 'RegExp', 'Set', 'String', 'Symbol',
6
16
  'WeakMap', 'WeakSet'
7
17
  ])
8
18
 
9
- function isErrConstructor(name) { return errors.has(name) }
10
-
11
19
  export default {
12
20
  meta: {
13
21
  type: 'problem',
@@ -19,22 +27,46 @@ export default {
19
27
  fixable: 'code',
20
28
  schema: [{
21
29
  type: 'string',
22
- enum: ['error-only', 'builtins'],
23
- description: 'Constructor categories to check: "error-only" (default) or "builtins".'
30
+ enum: ['errors', 'throwing', 'all', 'error-only', 'builtins'],
31
+ description: 'Constructor categories to check'
24
32
  }],
25
- defaultOptions: ['error-only'],
26
- messages: { requireNew: 'Use `new {{name}}()` instead of `{{name}}()`' }
33
+ defaultOptions: ['errors'],
34
+ messages: {
35
+ requireNew: 'Use `new {{name}}()` instead of `{{name}}()`',
36
+ deprecatedMode: `'{{old}}' is deprecated. Use '{{new}}' instead.`
37
+ }
27
38
  },
28
39
 
29
40
  create(context) {
30
- const mode = context.options[0] || 'error-only'
41
+ const optionAliases = deprecations.options['require-new'] || {},
42
+ rawMode = context.options[0] || 'errors'
43
+ /* eslint-disable security/detect-object-injection */
44
+ let mode = optionAliases[rawMode] || rawMode, deprecation
45
+ if (optionAliases[rawMode])
46
+ deprecation = { old: rawMode, new: optionAliases[rawMode] }
47
+ /* eslint-enable security/detect-object-injection */
48
+ else if (rawMode == 'builtins')
49
+ mode = 'legacy-builtins'
50
+
51
+ function shouldReportName(name) {
52
+ return mode == 'errors' ? errorConstructors.has(name)
53
+ : mode == 'throwing' ? errorConstructors.has(name) || throwingConstructors.has(name)
54
+ : mode == 'all' ? errorConstructors.has(name) || throwingConstructors.has(name)
55
+ || lenientConstructors.has(name)
56
+ : mode == 'legacy-builtins' ? errorConstructors.has(name) || legacyBuiltins.has(name)
57
+ : false
58
+ }
59
+
31
60
  return {
61
+ Program(node) {
62
+ if (deprecation)
63
+ context.report({
64
+ node, messageId: 'deprecatedMode', data: { old: deprecation.old, new: deprecation.new }})
65
+ },
32
66
  CallExpression(node) {
33
67
  if (node.new) return
34
68
  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)
69
+ if (shouldReportName(calleeName))
38
70
  context.report({
39
71
  node, messageId: 'requireNew', data: { name: calleeName },
40
72
  fix(fixer) { return fixer.insertTextBefore(node, 'new ') }