eslint-plugin-constructors 1.4.2 → 1.5.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
@@ -4,9 +4,9 @@
4
4
 
5
5
  <a target="_blank" href="https://www.npmjs.com/package/eslint-plugin-constructors">
6
6
  <img alt="[Latest Build]" src="https://img.shields.io/npm/v/eslint-plugin-constructors?logo=icinga&style=for-the-badge&label=Latest%20Build&color=32fcee"></a>
7
- <a href="#license">
7
+ <a href="https://codeberg.org/adamlui/eslint-plugin-constructors/src/branch/main/docs/LICENSE.md">
8
8
  <img alt="[License: MIT]" src="https://img.shields.io/badge/License-MIT-f1f20a.svg?logo=internetarchive&logoColor=white&labelColor=464646&style=for-the-badge"></a>
9
- <a>
9
+ <a target="_blank" href="https://getnpmstats.com/?package=eslint-plugin-constructors&range=last-year&interval=monthly">
10
10
  <img src="https://img.shields.io/npm/dm/eslint-plugin-constructors?style=for-the-badge&logo=npm&color=black"></a>
11
11
  <a target="_blank" href="https://eslint.org/">
12
12
  <img alt="[Requires ESLint >=9.0.0]" src="https://img.shields.io/badge/Requires-ESLint_%3E%3D9.0.0-4B32C3?style=for-the-badge&logo=eslint"></a>
@@ -46,7 +46,7 @@ export default [
46
46
  rules: {
47
47
  ...constructors.configs.recommended.rules,
48
48
  'constructors/no-explicit-return': 'off', // selectively disable rule
49
- 'constructors/require-new': ['error', { mode: 'throwing' }] // selectively tewak rule
49
+ 'constructors/require-new': ['error', { mode: 'throwing' }] // selectively tweak rule
50
50
  }
51
51
  }
52
52
  ]
@@ -63,24 +63,146 @@ Available bundles: `recommended`, `problem` (deterministic bugs only)
63
63
  Supported rules
64
64
  </h2>
65
65
 
66
- | ID | Description | Auto-fixable | Recommended |
67
- | --- | --- | :-: | :-: |
68
- | [`no-explicit-return`](#no-explicit-return) | Disallows explicit `return` statements inside class constructors | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> |
69
- | [`require-error-subclass-name`](#require-error-subclass-name) | Requires classes extending `Error` have a name ending in `Error` | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> |
70
- | [`require-new`](#require-new) | Requires `new` when calling error constructors (and optionally other built-ins) | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> | <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> |
66
+ <h3 id="no-constructor-bind">
67
+ <code>no-constructor-bind</code>
68
+ <a title="Introduced in recent release" target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/releases/tag/v1.5.0">
69
+ <img height=16 align="middle" src="https://api.iconify.design/mdi/sprout.svg?color=%234a4a4a"></a>
70
+ <a title="Included in recommended config" href="#usage">
71
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
72
+ <a title="Accepts eslint --fix" target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface#--fix">
73
+ <img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
74
+ <a title="Doesn't prevent bugs" href="#no-constructor-bind">
75
+ <img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
76
+ <a title="Copy rule URL" target="_blank"
77
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-constructors%2F%23no-constructor-bind">
78
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
79
+ </h3>
80
+
81
+ <details>
82
+
83
+ <summary>Disallows binding methods to <code>this</code> inside class constructors (use class arrow functions instead).</summary>
84
+
85
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
86
+
87
+ ```diff
88
+ class User {
89
+ constructor() {
90
+ ! this.greet = this.greet.bind(this)
91
+ }
92
+ greet() { return 'Hello' }
93
+ }
94
+ ```
95
+
96
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> Correct code
97
+
98
+ ```diff
99
+ class User {
100
+ - constructor() {
101
+ - this.greet = this.greet.bind(this)
102
+ - }
103
+ + greet = () => 'Hello'
104
+ }
105
+ ```
106
+
107
+ #### When not to use it
108
+
109
+ If you target environments that do not support class fields.
110
+
111
+ #### Introduced in
112
+
113
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/releases/tag/v1.5.0">v1.5.0</a> (October 19, 2026)
114
+
115
+ #### See also
116
+
117
+ - <img height=12 src="https://www.google.com/s2/favicons?domain=developer.mozilla.org&sz=64">&ensp;<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind">MDN — `Function.prototype.bind()`</a>
118
+ - <img height=12 src="https://www.google.com/s2/favicons?domain=developer.mozilla.org&sz=64">&ensp;<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields">MDN — Public class fields</a>
119
+
120
+ <div align="center">
121
+
122
+ [Back to index ↑][epc-rules]
123
+
124
+ </div>
71
125
 
72
126
  ---
73
127
 
128
+ </details>
129
+
130
+ <h3 id="no-constructor-state">
131
+ <code>no-constructor-state</code>
132
+ <a title="Introduced in recent release" target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/releases/tag/v1.5.0">
133
+ <img height=16 align="middle" src="https://api.iconify.design/mdi/sprout.svg?color=%234a4a4a"></a>
134
+ <a title="Included in recommended config" href="#usage">
135
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
136
+ <a title="Accepts eslint --fix" target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface#--fix">
137
+ <img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
138
+ <a title="Doesn't prevent bugs" href="#no-constructor-state">
139
+ <img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
140
+ <a title="Copy rule URL" target="_blank"
141
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-constructors%2F%23no-constructor-state">
142
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
143
+ </h3>
144
+
145
+ <details>
146
+
147
+ <summary>Disallows assigning simple initial state to <code>this</code> inside class constructors (use class fields instead).</summary>
148
+
149
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
150
+
151
+ ```diff
152
+ class Component {
153
+ constructor() {
154
+ ! this.state = { ready: false }
155
+ }
156
+ }
157
+ ```
158
+
159
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> Correct code
160
+
161
+ ```diff
162
+ class Component {
163
+ - constructor() {
164
+ - this.state = { ready: false }
165
+ - }
166
+ + state = { ready: false }
167
+ }
168
+ ```
169
+
170
+ Dynamic initialization (e.g. `this.state = createInitialState()`) is **allowed** by default, because the value is not a simple literal.
171
+
172
+ #### When not to use it
173
+
174
+ If you target environments that do not support class fields.
175
+
176
+ #### Introduced in
177
+
178
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/releases/tag/v1.5.0">v1.5.0</a> (October 19, 2026)
179
+
180
+ #### See also
181
+
182
+ - <img height=12 src="https://www.google.com/s2/favicons?domain=developer.mozilla.org&sz=64">&ensp;<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields">MDN — Public class fields</a>
183
+
184
+ <div align="center">
185
+
186
+ [Back to index ↑][epc-rules]
187
+
188
+ </div>
189
+
190
+ ---
191
+
192
+ </details>
193
+
74
194
  <h3 id="no-explicit-return">
75
195
  <code>no-explicit-return</code>
76
196
  <a title="Included in recommended config" href="#usage">
77
197
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
78
- <a title="Copy rule URL" target="_blank"
198
+ <a title="Copy rule URL" target="_blank"
79
199
  href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-constructors%2F%23no-explicit-return">
80
200
  <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
81
201
  </h3>
82
202
 
83
- Disallows explicit `return` statements inside class constructors.
203
+ <details>
204
+
205
+ <summary>Disallows explicit <code>return</code> statements inside class constructors.</summary>
84
206
 
85
207
  #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
86
208
 
@@ -106,7 +228,7 @@ Disallows explicit `return` statements inside class constructors.
106
228
  - return
107
229
  + this.x = 1
108
230
  }
109
-
231
+
110
232
  class B {
111
233
  constructor() {
112
234
  - return this
@@ -119,6 +241,14 @@ Disallows explicit `return` statements inside class constructors.
119
241
 
120
242
  If you need to return a different object from a constructor.
121
243
 
244
+ #### Introduced in
245
+
246
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/releases/tag/v1.0.0">v1.0.0</a> (September 9, 2026)
247
+
248
+ #### See also
249
+
250
+ - <img height=12 src="https://www.google.com/s2/favicons?domain=developer.mozilla.org&sz=64">&ensp;<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor">MDN — Class `constructor`</a>
251
+
122
252
  <div align="center">
123
253
 
124
254
  [Back to index ↑][epc-rules]
@@ -127,18 +257,22 @@ If you need to return a different object from a constructor.
127
257
 
128
258
  ---
129
259
 
260
+ </details>
261
+
130
262
  <h3 id="require-error-subclass-name">
131
263
  <code>require-error-subclass-name</code>
132
264
  <a title="Included in recommended config" href="#usage">
133
265
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
134
- <a title="Doesn't prevent bugs" href="#meta-spacing">
266
+ <a title="Doesn't prevent bugs" href="#require-error-subclass-name">
135
267
  <img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
136
- <a title="Copy rule URL" target="_blank"
268
+ <a title="Copy rule URL" target="_blank"
137
269
  href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-constructors%2F%23require-error-subclass-name">
138
270
  <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
139
271
  </h3>
140
272
 
141
- Requires classes extending `Error` have a name ending in `Error`.
273
+ <details>
274
+
275
+ <summary>Requires classes extending <code>Error</code> have a name ending in <code>Error</code>.</summary>
142
276
 
143
277
  #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
144
278
 
@@ -160,6 +294,14 @@ Requires classes extending `Error` have a name ending in `Error`.
160
294
 
161
295
  If you don't follow the convention of suffixing custom error classes w/ `Error`.
162
296
 
297
+ #### Introduced in
298
+
299
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/releases/tag/v1.0.0">v1.0.0</a> (September 9, 2026)
300
+
301
+ #### See also
302
+
303
+ - <img height=12 src="https://www.google.com/s2/favicons?domain=developer.mozilla.org&sz=64">&ensp;<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error">MDN — `Error`</a>
304
+
163
305
  <div align="center">
164
306
 
165
307
  [Back to index ↑][epc-rules]
@@ -168,6 +310,8 @@ If you don't follow the convention of suffixing custom error classes w/ `Error`.
168
310
 
169
311
  ---
170
312
 
313
+ </details>
314
+
171
315
  <h3 id="require-new">
172
316
  <code>require-new</code>
173
317
  <a title="Included in recommended config" href="#usage">
@@ -176,16 +320,18 @@ If you don't follow the convention of suffixing custom error classes w/ `Error`.
176
320
  <img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
177
321
  <a title="Accepts array w/ options" href="#usage">
178
322
  <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
179
- <a title="Copy rule URL" target="_blank"
323
+ <a title="Copy rule URL" target="_blank"
180
324
  href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-constructors%2F%23require-new">
181
325
  <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
182
326
  </h3>
183
327
 
184
- Requires `new` when calling error constructors (and optionally other built-ins).
328
+ <details>
329
+
330
+ <summary>Requires <code>new</code> when calling error constructors (and optionally other built-ins).</summary>
185
331
 
186
332
  #### Options
187
333
 
188
- - `mode` (default `'errors'`) – constructor category to enforce `new` on (`'errors'`, `'throwing'`, `'all'`)
334
+ - `mode` (default `'errors'`) – constructor category to enforce `new` on (`'errors'`, `'throwing'`, `'all'`) <a title="Introduced in v1.0.0" target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/releases/tag/v1.0.0"><img height=14 alt="[Since v1.0.0]" src="https://img.shields.io/badge/since-v1.0.0-8a9199?style=flat-square" align="middle"></a>
189
335
 
190
336
  #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
191
337
 
@@ -220,12 +366,23 @@ Requires `new` when calling error constructors (and optionally other built-ins).
220
366
 
221
367
  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.
222
368
 
369
+ #### Introduced in
370
+
371
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/releases/tag/v1.0.0">v1.0.0</a> (September 9, 2026)
372
+
373
+ #### See also
374
+
375
+ - <img height=12 src="https://www.google.com/s2/favicons?domain=developer.mozilla.org&sz=64">&ensp;<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new">MDN — `new` operator</a>
376
+ - <img height=12 src="https://www.google.com/s2/favicons?domain=developer.mozilla.org&sz=64">&ensp;<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error">MDN — `Error` objects</a>
377
+
223
378
  <div align="center">
224
379
 
225
380
  [Back to index ↑][epc-rules]
226
381
 
227
382
  </div>
228
383
 
384
+ </details>
385
+
229
386
  ---
230
387
 
231
388
  ## <img width=30 align="top" src="https://api.iconify.design/boxicons/code-alt-filled.svg?color=%232b2b2a"> Shield embed
@@ -250,8 +407,8 @@ If you prefer calling built-ins w/o `new` (which the runtime allows for some of
250
407
 
251
408
  ## <img height=30 width=27 align="top" src="https://api.iconify.design/ic/baseline-account-balance.svg?color=%232b2b2a"> License
252
409
 
253
- <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/src/branch/main/docs/LICENSE.md">
254
- <img height=85 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/9e820e5d51/assets/images/logos/mit-license/red-pixelated.png"></a>
410
+ <a href="https://codeberg.org/adamlui/eslint-plugin-constructors/src/branch/main/docs/LICENSE.md">
411
+ <img height=88 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-chrome-extensions/d79e1db7e2/assets/images/banners/mit-license/banner.png"></a>
255
412
  <br><br>
256
413
 
257
414
  <details>
package/docs/LICENSE.md CHANGED
@@ -1,4 +1,4 @@
1
- # 🏛️ MIT License
1
+ # MIT License
2
2
 
3
3
  **Copyright © 2026 [Adam Lui](https://codeberg.org/adamlui).**
4
4
 
package/package.json CHANGED
@@ -1,22 +1,25 @@
1
1
  {
2
2
  "name": "eslint-plugin-constructors",
3
- "version": "1.4.2",
3
+ "version": "1.5.0",
4
4
  "description": "ESLint rules for constructor usage",
5
- "type": "module",
6
- "engines": {
7
- "node": ">=18.18.0"
8
- },
9
- "author": {
10
- "name": "Adam Lui",
11
- "email": "adam@kudoai.org",
12
- "url": "https://codeberg.org/adamlui"
13
- },
5
+ "keywords": [
6
+ "code-quality",
7
+ "constructor",
8
+ "error",
9
+ "eslint",
10
+ "eslint-plugin",
11
+ "eslintplugin",
12
+ "security"
13
+ ],
14
14
  "homepage": "https://codeberg.org/adamlui/eslint-plugin-constructors/#readme",
15
15
  "bugs": {
16
16
  "url": "https://codeberg.org/adamlui/eslint-plugin-constructors/issues",
17
17
  "email": "support@adamlui.com"
18
18
  },
19
- "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://codeberg.org/adamlui/eslint-plugin-constructors.git"
22
+ },
20
23
  "funding": [
21
24
  {
22
25
  "type": "ko-fi",
@@ -27,49 +30,43 @@
27
30
  "url": "https://paypal.me/adamlui"
28
31
  }
29
32
  ],
30
- "main": "src/index.js",
33
+ "license": "MIT",
34
+ "author": {
35
+ "name": "Adam Lui",
36
+ "email": "adam@kudoai.org",
37
+ "url": "https://codeberg.org/adamlui"
38
+ },
39
+ "type": "module",
40
+ "exports": {
41
+ ".": {
42
+ "types": "./src/index.d.ts",
43
+ "default": "./src/index.js"
44
+ }
45
+ },
46
+ "main": "./src/index.js",
47
+ "types": "./src/index.d.ts",
31
48
  "files": [
32
49
  "docs/",
33
50
  "src/"
34
51
  ],
35
- "types": "src/index.d.ts",
36
52
  "scripts": {
37
- "prepare": "husky",
38
- "lint": "eslint . --cache && yamllint .",
39
- "lint:all": "eslint . && yamllint .",
40
- "lint:fix": "eslint . --fix --cache && yamllint .",
41
- "lint:fix-all": "eslint . --fix && yamllint .",
42
- "test": "node tests/run.js",
43
- "demo": "node demo/run.js",
44
- "bump:patch": "bash utils/bump.sh patch",
45
- "bump:minor": "bash utils/bump.sh minor",
46
53
  "bump:feat": "npm run bump:minor",
47
- "bump:major": "bash utils/bump.sh major"
48
- },
49
- "repository": {
50
- "type": "git",
51
- "url": "git+https://codeberg.org/adamlui/eslint-plugin-constructors.git"
54
+ "bump:major": "bash utils/bump.sh major",
55
+ "bump:minor": "bash utils/bump.sh minor",
56
+ "bump:patch": "bash utils/bump.sh patch",
57
+ "demo": "eslint demo",
58
+ "lint": "pre-commit run",
59
+ "lint:all": "pre-commit run --all-files",
60
+ "test": "node tests/run.js"
52
61
  },
53
- "keywords": [
54
- "code-quality",
55
- "constructor",
56
- "error",
57
- "eslint",
58
- "eslint-plugin",
59
- "eslintplugin",
60
- "security"
61
- ],
62
62
  "dependencies": {
63
- "globals": "^17.12.0"
64
- },
65
- "peerDependencies": {
66
- "eslint": ">=9.0.0"
63
+ "globals": "^17.13.0"
67
64
  },
68
65
  "devDependencies": {
69
66
  "@e18e/eslint-plugin": "^0.8.1",
70
67
  "@eslint/js": "^10.0.1",
71
- "@eslint/json": "^2.1.0",
72
- "@eslint/markdown": "^8.0.3",
68
+ "@eslint/json": "^2.1.1",
69
+ "@eslint/markdown": "^7.5.1",
73
70
  "@stylistic/eslint-plugin": "^5.10.0",
74
71
  "eslint": "^10.12.0",
75
72
  "eslint-plugin-eslint-plugin": "^7.6.2",
@@ -77,10 +74,17 @@
77
74
  "eslint-plugin-n": "^18.4.1",
78
75
  "eslint-plugin-no-secrets": "^2.3.3",
79
76
  "eslint-plugin-no-unsanitized": "^4.1.5",
77
+ "eslint-plugin-package-json": "^1.10.1",
80
78
  "eslint-plugin-promise": "^7.3.0",
81
79
  "eslint-plugin-regexp": "^3.3.1",
82
80
  "eslint-plugin-security": "^4.2.0",
83
- "husky": "^9.1.7",
84
- "yamllint-js": "^0.2.4"
81
+ "eslint-plugin-sonarjs": "^4.2.2",
82
+ "eslint-plugin-woodpecker": "^1.0.1"
83
+ },
84
+ "peerDependencies": {
85
+ "eslint": ">=9.0.0"
86
+ },
87
+ "engines": {
88
+ "node": ">=18.18.0"
85
89
  }
86
90
  }
@@ -1,10 +1,9 @@
1
- // Delete a line to end support
2
-
3
- export const rules = { // old: new
4
- 'require-new': {
5
- 'require-new': { mode: 'errors', // 2026-9-24 announced certain default switch + possible unnamed syntax removal
6
- builtins: 'all', 'error-only': 'errors' } // 2026-9-13 announced removal @ v2
1
+ export default {
2
+ rules: {
3
+ 'require-new': {
4
+ default: { mode: 'errors' }, // 2026-9-24 announced default switch
5
+ wrapKey: 'mode', // 2026-9-24 announced potential removal
6
+ renames: { builtins: 'all', 'error-only': 'errors' } // 2026-9-13 announced removal @ v2
7
+ }
7
8
  }
8
9
  }
9
-
10
- export default { rules }
package/src/index.d.ts CHANGED
@@ -1,6 +1,30 @@
1
+ type RuleSeverity = 0 | 1 | 2 | 'off' | 'warn' | 'error'
2
+ type RuleSetting = RuleSeverity | [RuleSeverity, ...unknown[]]
3
+
4
+ type ConfigName =
5
+ | 'recommended'
6
+ | 'problem'
7
+
8
+ interface PluginConfig {
9
+ plugins: Record<string, unknown>
10
+ rules: Record<string, RuleSetting>
11
+ }
12
+
13
+ interface PluginRule {
14
+ meta: {
15
+ type?: 'problem' | 'suggestion' | 'layout'
16
+ deprecated?: boolean | {
17
+ message?: string
18
+ replacedBy?: Array<{ rule: { name: string } }>
19
+ }
20
+ docs?: Record<string, unknown>
21
+ }
22
+ create: (context: unknown) => Record<string, (...args: unknown[]) => void>
23
+ }
24
+
1
25
  declare const plugin: {
2
- rules: Record<string, unknown>
3
- configs: { recommended: Record<string, unknown> }
26
+ rules: Record<string, PluginRule>
27
+ configs: Record<ConfigName, PluginConfig>
4
28
  }
5
29
 
6
- export default plugin
30
+ export { plugin as default, plugin as constructors }
package/src/index.js CHANGED
@@ -1,34 +1,9 @@
1
- import { rules as oldRules } from './data/deprecations.js'
1
+ import init from './utils/init.js'
2
2
 
3
- import noExplicitReturn from './rules/no-explicit-return.js'
4
- import requireErrorSubclassName from './rules/require-error-subclass-name.js'
5
- import requireNew from './rules/require-new.js'
3
+ const plugin = init.plugin()
6
4
 
7
- import { bundleRules, initDeprecatedAlias } from './utils.js'
8
-
9
- const plugin = {
10
- rules: {
11
- 'no-explicit-return': noExplicitReturn,
12
- 'require-error-subclass-name': requireErrorSubclassName,
13
- 'require-new': requireNew
14
- }
15
- }
16
-
17
- for (const [ruleId, rule] of Object.entries(plugin.rules))
18
- rule.meta.docs.url = `https://codeberg.org/adamlui/eslint-plugin-constructors/#${ruleId}`
19
-
20
- for (const entry of Object.entries(oldRules))
21
- initDeprecatedAlias(plugin, entry)
22
-
23
- plugin.configs = {
24
- recommended: {
25
- plugins: { constructors: plugin },
26
- rules: bundleRules(plugin.rules, rule => rule.meta.docs.recommended, 'constructors')
27
- },
28
- problem: {
29
- plugins: { constructors: plugin },
30
- rules: bundleRules(plugin.rules, rule => rule.meta.type == 'problem', 'constructors')
31
- }
32
- }
5
+ init.rules(plugin)
6
+ init.deprecations(plugin)
7
+ init.configs(plugin)
33
8
 
34
9
  export { plugin as default, plugin as constructors }
@@ -0,0 +1,60 @@
1
+ import ast from '../utils/ast.js'
2
+
3
+ const re = { superOrArguments: /\bsuper\b|\barguments\b/u }
4
+
5
+ export default {
6
+ meta: {
7
+ type: 'suggestion',
8
+ docs: {
9
+ description: 'Disallow binding methods to `this` in class constructors',
10
+ category: 'Best Practices',
11
+ recommended: true
12
+ },
13
+ fixable: 'code',
14
+ schema: [],
15
+ messages: { unexpectedBind: 'Use an arrow class field instead of binding `{{name}}` in the constructor.' }
16
+ },
17
+
18
+ create(context) {
19
+ const sourceCode = context.sourceCode
20
+ return {
21
+ AssignmentExpression(node) {
22
+ if (node.operator != '=') return
23
+ const { left, right } = node
24
+ if (left.type != 'MemberExpression' || left.computed || left.object.type != 'ThisExpression') return
25
+ const methodName = left.property.name ; if (!methodName) return
26
+ if (right.type != 'CallExpression'
27
+ || right.arguments.length != 1
28
+ || right.arguments[0].type != 'ThisExpression'
29
+ ) return
30
+ const bindCallee = right.callee
31
+ if (bindCallee.type != 'MemberExpression'
32
+ || bindCallee.computed
33
+ || bindCallee.property.name != 'bind'
34
+ || bindCallee.object.type != 'MemberExpression'
35
+ || bindCallee.object.computed
36
+ || bindCallee.object.object.type != 'ThisExpression'
37
+ || bindCallee.object.property.name != methodName
38
+ || !ast.isInsideConstructor(node)
39
+ ) return
40
+ context.report({
41
+ node, messageId: 'unexpectedBind', data: { name: methodName },
42
+ fix(fixer) {
43
+ const stmt = node.parent ; if (stmt?.type != 'ExpressionStatement') return
44
+ const classBody = ast.getEnclosingClassBody(node)
45
+ if (!classBody || classBody.loc.start.line == classBody.loc.end.line) return
46
+ const method = ast.findUniqueMethod(classBody, methodName)
47
+ if (!method || re.superOrArguments.test(sourceCode.getText(method.value))) return
48
+ const params = method.value.params.map(param => sourceCode.getText(param)).join(', ')
49
+ return [
50
+ fixer.replaceText(method,
51
+ `${sourceCode.getText(method.key)} = (${params}) => ${
52
+ sourceCode.getText(method.value.body)}`),
53
+ fixer.removeRange(ast.getLineAwareRemovalRange(sourceCode, stmt))
54
+ ]
55
+ }
56
+ })
57
+ }
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,87 @@
1
+ import ast from '../utils/ast.js'
2
+
3
+ const simpleIdentifierValues = new Set(['Infinity', 'NaN', 'undefined']),
4
+ simpleUnaryOperators = new Set(['!', '+', '-', '~', 'typeof', 'void'])
5
+
6
+ function getStateAssignment(stmt) {
7
+ if (stmt.type != 'ExpressionStatement') return
8
+ const assignment = stmt.expression
9
+ if (assignment.type != 'AssignmentExpression' || assignment.operator != '=') return
10
+ const { left, right } = assignment
11
+ if (left.type != 'MemberExpression' || left.computed || left.object.type != 'ThisExpression') return
12
+ if (!left.property.name || !isSimpleValue(right)) return
13
+ return assignment
14
+ }
15
+
16
+ function isSimpleValue(node) {
17
+ switch (node?.type) {
18
+ case 'Literal':
19
+ return true
20
+ case 'TemplateLiteral':
21
+ return node.expressions.length == 0
22
+ case 'Identifier':
23
+ return simpleIdentifierValues.has(node.name)
24
+ case 'UnaryExpression':
25
+ return simpleUnaryOperators.has(node.operator)
26
+ && (node.argument.type == 'Literal'
27
+ || (node.argument.type == 'Identifier' && simpleIdentifierValues.has(node.argument.name)))
28
+ case 'ArrayExpression':
29
+ return node.elements.every(element => element == null || isSimpleValue(element))
30
+ case 'ObjectExpression':
31
+ return node.properties.every(property =>
32
+ property.type == 'Property' && property.kind == 'init'
33
+ && !property.computed && !property.method && isSimpleValue(property.value))
34
+ default:
35
+ return false
36
+ }
37
+ }
38
+
39
+ function isSuperCall(stmt) {
40
+ return stmt.type == 'ExpressionStatement' && stmt.expression.type == 'CallExpression'
41
+ && stmt.expression.callee.type == 'Super'
42
+ }
43
+
44
+ export default {
45
+ meta: {
46
+ type: 'suggestion',
47
+ docs: {
48
+ description: 'Disallow assigning simple initial state to `this` in class constructors',
49
+ category: 'Best Practices',
50
+ recommended: true
51
+ },
52
+ fixable: 'code',
53
+ schema: [],
54
+ messages: { useClassField: 'Use a class field instead of assigning `{{name}}` in the constructor.' }
55
+ },
56
+
57
+ create(context) {
58
+ const sourceCode = context.sourceCode
59
+ return {
60
+ MethodDefinition(node) {
61
+ if (node.kind != 'constructor' || node.static) return
62
+ const statements = node.value.body.body
63
+ statements.forEach((stmt, idx) => {
64
+ const assignment = getStateAssignment(stmt) ; if (!assignment) return
65
+ const name = assignment.left.property.name
66
+ context.report({
67
+ node: assignment, messageId: 'useClassField', data: { name },
68
+ fix(fixer) {
69
+ const classBody = node.parent
70
+ if (classBody?.type != 'ClassBody' || classBody.loc.start.line == classBody.loc.end.line)
71
+ return
72
+ const safePrefix = statements.slice(0, idx)
73
+ .every(prev => isSuperCall(prev) || getStateAssignment(prev))
74
+ if (!safePrefix) return
75
+ const closeBrace = sourceCode.getLastToken(classBody)
76
+ return [
77
+ fixer.insertTextBefore(closeBrace, `${ast.getLineIndent(sourceCode, node)}${name} = ${
78
+ sourceCode.getText(assignment.right)}\n`),
79
+ fixer.removeRange(ast.getLineAwareRemovalRange(sourceCode, stmt))
80
+ ]
81
+ }
82
+ })
83
+ })
84
+ }
85
+ }
86
+ }
87
+ }
@@ -15,12 +15,10 @@ export default {
15
15
  if (!node.id || !node.superClass?.name?.endsWith('Error')) return
16
16
  checkName(node.id.name, node.id)
17
17
  }
18
-
19
18
  function checkName(name, node) {
20
19
  if (!name || name.endsWith('Error')) return
21
20
  context.report({ node, messageId: 'endWithError' })
22
21
  }
23
-
24
22
  function checkVariableDeclarator(node) {
25
23
  const classExpr = node.init
26
24
  if (classExpr?.type != 'ClassExpression'
@@ -29,7 +27,6 @@ export default {
29
27
  ) return
30
28
  checkName(node.id.name, node.id)
31
29
  }
32
-
33
30
  return {
34
31
  ClassDeclaration: checkClassNode,
35
32
  ClassExpression: checkClassNode,
@@ -1,14 +1,14 @@
1
1
  import globals from 'globals'
2
2
 
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'])
3
+ const constructors = {
4
+ errors: new Set(Object.keys(globals.builtin).filter(name => name.endsWith('Error'))),
5
+ lenient: new Set(['Array', 'Date', 'Function', 'Object', 'RegExp']),
6
+ throwing: 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
12
 
13
13
  export default {
14
14
  meta: {
@@ -37,10 +37,10 @@ export default {
37
37
  create(context) {
38
38
  const mode = context.options[0]?.mode ?? 'throwing'
39
39
  function shouldReportName(name) {
40
- return mode == 'errors' ? errorConstructors.has(name)
41
- : mode == 'throwing' ? errorConstructors.has(name) || throwingConstructors.has(name)
42
- : mode == 'all' ? errorConstructors.has(name) || throwingConstructors.has(name)
43
- || lenientConstructors.has(name)
40
+ return mode == 'errors' ? constructors.errors.has(name)
41
+ : mode == 'throwing' ? constructors.errors.has(name) || constructors.throwing.has(name)
42
+ : mode == 'all' ? constructors.errors.has(name) || constructors.throwing.has(name)
43
+ || constructors.lenient.has(name)
44
44
  : false
45
45
  }
46
46
  return {
@@ -0,0 +1,49 @@
1
+ const funcTypes = new Set(['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression']),
2
+ re = { wsOnly: /^\s*$/ }
3
+
4
+ export default {
5
+ findUniqueMethod(classBody, methodName) {
6
+ const matches = classBody.body.filter(member =>
7
+ member.type == 'MethodDefinition' && !member.static && !member.computed
8
+ && member.key.type == 'Identifier' && member.key.name == methodName)
9
+ if (matches.length != 1) return
10
+ const method = matches[0]
11
+ if (method.kind != 'method' || method.value.async || method.value.generator) return
12
+ return method
13
+ },
14
+
15
+ getEnclosingClassBody(node) {
16
+ let current = node.parent
17
+ while (current) {
18
+ if (current.type == 'ClassBody') return current
19
+ current = current.parent
20
+ }
21
+ },
22
+
23
+ getLineAwareRemovalRange(sourceCode, node) {
24
+ const text = sourceCode.text,
25
+ start = node.range[0],
26
+ lineStart = text.lastIndexOf('\n', start - 1) + 1
27
+ if (!re.wsOnly.test(text.slice(lineStart, start))) return node.range
28
+ return [lineStart > 0 ? lineStart - 1 : 0, node.range[1]]
29
+ },
30
+
31
+ getLineIndent(sourceCode, node) {
32
+ const line = sourceCode.lines[node.loc.start.line - 1] ?? ''
33
+ return line.slice(0, line.length - line.trimStart().length)
34
+ },
35
+
36
+ isInsideConstructor(node) {
37
+ let current = node.parent
38
+ while (current) {
39
+ if (funcTypes.has(current.type))
40
+ return ( // whether this func is constructor's val
41
+ current.parent?.type == 'MethodDefinition'
42
+ && current.parent.kind == 'constructor'
43
+ && !current.parent.static
44
+ )
45
+ current = current.parent
46
+ }
47
+ return false
48
+ }
49
+ }
@@ -0,0 +1,51 @@
1
+ /* eslint-disable security/detect-object-injection */
2
+
3
+ export default {
4
+ initAlias(plugin, [ruleId, { default: defaultOpts, wrapKey, renames = {} }]) {
5
+ const source = plugin.rules[ruleId] ; if (!source) return
6
+ plugin.rules[ruleId] = {
7
+ meta: {
8
+ ...source.meta,
9
+ schema: [{ type: ['object', 'string'] }],
10
+ defaultOptions: [],
11
+ messages: {
12
+ ...source.meta.messages,
13
+ deprecatedPositional: 'Passing a bare string is deprecated. '
14
+ + `Use \`['error', { ${wrapKey}: '{{wrapped}}' }]\` instead.`,
15
+ deprecatedValue: `'{{old}}' is deprecated. Use '{{new}}' instead.`
16
+ }
17
+ },
18
+ create(context) {
19
+ const raw = context.options[0]
20
+ let newOpts, warnings = []
21
+ if (raw === undefined) // no opts passed → apply preserved old default
22
+ newOpts = defaultOpts
23
+ else if (typeof raw == 'string') { // old pos syntax → wrap in obj + rename
24
+ const wrapped = renames[raw] ?? raw
25
+ newOpts = { [wrapKey]: wrapped }
26
+ warnings.push({ messageId: 'deprecatedPositional', data: { wrapped } })
27
+ if (renames[raw])
28
+ warnings.push({ messageId: 'deprecatedValue', data: { old: raw, new: wrapped } })
29
+ } else if (raw !== null && typeof raw == 'object') // new obj syntax → pass thru unchanged
30
+ newOpts = raw
31
+ else
32
+ newOpts = defaultOpts
33
+ const aliasContext = Object.create(context)
34
+ Object.defineProperty(aliasContext, 'options', {
35
+ value: newOpts === undefined ? [] : [newOpts],
36
+ writable: true, configurable: true, enumerable: true
37
+ })
38
+ const visitors = { ...source.create(aliasContext) }
39
+ if (warnings.length > 0) {
40
+ const originalProgram = visitors.Program
41
+ visitors.Program = function(node) {
42
+ for (const warning of warnings)
43
+ context.report({ node, messageId: warning.messageId, data: warning.data })
44
+ if (originalProgram) originalProgram.call(this, node)
45
+ }
46
+ }
47
+ return visitors
48
+ }
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,47 @@
1
+ import deprecationsUtils from './deprecations.js'
2
+ import rulesUtils from './rules.js'
3
+
4
+ import noConstructorBind from '../rules/no-constructor-bind.js'
5
+ import noConstructorState from '../rules/no-constructor-state.js'
6
+ import noExplicitReturn from '../rules/no-explicit-return.js'
7
+ import requireErrorSubclassName from '../rules/require-error-subclass-name.js'
8
+ import requireNew from '../rules/require-new.js'
9
+
10
+ import deprecations from '../data/deprecations.js'
11
+
12
+ const namespace = 'constructors'
13
+
14
+ export default {
15
+ configs(plugin) {
16
+ const plugins = { [namespace]: plugin },
17
+ rules = plugin.rules,
18
+ createConfig = (predicate, { language, plugins: extraPlugins = {} } = {}) => ({
19
+ plugins: { ...plugins, ...extraPlugins },
20
+ ...(language ? { language } : {}),
21
+ rules: rulesUtils.bundle(rules, predicate, namespace)
22
+ })
23
+ plugin.configs = {
24
+ recommended: createConfig(rule => rule.meta.docs.recommended),
25
+ problem: createConfig(rule => rule.meta.type == 'problem')
26
+ }
27
+ },
28
+
29
+ deprecations(plugin) {
30
+ for (const entry of Object.entries(deprecations))
31
+ deprecationsUtils.initAlias(plugin, entry)
32
+ },
33
+
34
+ plugin() { return { rules: {}, configs: {} }},
35
+
36
+ rules(plugin) {
37
+ plugin.rules = {
38
+ 'no-constructor-bind': noConstructorBind,
39
+ 'no-constructor-state': noConstructorState,
40
+ 'no-explicit-return': noExplicitReturn,
41
+ 'require-error-subclass-name': requireErrorSubclassName,
42
+ 'require-new': requireNew
43
+ }
44
+ for (const [ruleId, rule] of Object.entries(plugin.rules))
45
+ rule.meta.docs.url = `https://codeberg.org/adamlui/eslint-plugin-${namespace}/#${ruleId}`
46
+ }
47
+ }
@@ -0,0 +1,7 @@
1
+ export default {
2
+ bundle(rules, predicate, pluginName) {
3
+ return Object.fromEntries(Object.entries(rules)
4
+ .filter(([, rule]) => !rule.meta.deprecated && predicate(rule))
5
+ .map(([ruleId]) => [`${pluginName}/${ruleId}`, 'error']))
6
+ }
7
+ }
package/src/utils.js DELETED
@@ -1,80 +0,0 @@
1
- export function bundleRules(rules, predicate, pluginName) {
2
- return Object.fromEntries(Object.entries(rules)
3
- .filter(([, rule]) => predicate(rule))
4
- .map(([ruleId]) => [`${pluginName}/${ruleId}`, 'error']))
5
- }
6
-
7
- export function initDeprecatedAlias(plugin, [oldName, alias]) { /* eslint-disable security/detect-object-injection */
8
- const routes = typeof alias == 'string' ? { [alias]: null } // 'old-rule': 'new-rule'
9
- : Array.isArray(alias) ? Object.fromEntries(alias.map(entry =>
10
- typeof entry == 'string' ? [entry, null] // ['new-rule-1', 'new-rule-2']
11
- : Object.entries(entry)[0])) // [{ 'new-rule': { 'old-opt': 'new-opt' }}]
12
- : alias // { 'new-rule': null | { 'new-opt': default } | { 'old-val': 'new-val' } | { ..., 'new-opt': default }}
13
- const targetNames = Object.keys(routes),
14
- isRename = !targetNames.includes(oldName),
15
- originals = Object.fromEntries(targetNames.map(name => [name, plugin.rules[name]])),
16
- source = originals[oldName] ?? originals[targetNames[0]]
17
- plugin.rules[oldName] = {
18
- meta: {
19
- ...source.meta,
20
- defaultOptions: [],
21
- schema: [{ type: ['object', 'array', 'string', 'number', 'boolean', 'null'] }],
22
- docs: { ...source.meta.docs, recommended: isRename ? false : source.meta.docs.recommended },
23
- messages: {
24
- ...source.meta.messages,
25
- deprecatedOption: `'{{old}}' is deprecated. Use '{{new}}' instead.`
26
- },
27
- ...(isRename ? { deprecated: {
28
- message: `Use ${targetNames.map(name => `\`${name}\``).join(', ')} instead.`,
29
- replacedBy: targetNames.map(name => ({ rule: { name }}))
30
- }} : {})
31
- },
32
- create(context) {
33
- const opts = context.options[0],
34
- isObj = opts?.constructor === Object
35
- return Object.assign({}, ...Object.entries(routes).map(([targetName, mapping]) => {
36
- let newOpts = opts, deprecation
37
- if (mapping) {
38
- const entries = Object.entries(mapping),
39
- namedRemap = entries.every(([, val]) => typeof val == 'string'),
40
- wrapKey = namedRemap && entries.length > 1 ? entries.at(-1)[0] : null,
41
- valMap = wrapKey ? Object.fromEntries(entries.slice(0, -1)) : mapping
42
- if (namedRemap) {
43
- if (typeof opts == 'string') { // bare old val → remap, optionally wrapped
44
- if (valMap[opts]) deprecation = { old: opts, new: valMap[opts] }
45
- const remapped = valMap[opts] ?? opts
46
- newOpts = wrapKey ? { [wrapKey]: remapped } : remapped
47
- } else if (wrapKey) { // object opts (or defaults) → remap inner val, warn if deprecated
48
- const raw = isObj ? (opts[wrapKey] ?? mapping[wrapKey]) : mapping[wrapKey],
49
- remapped = valMap[raw]
50
- if (remapped) deprecation = { old: raw, new: remapped }
51
- newOpts = { [wrapKey]: remapped ?? raw }
52
- } else { // object opts → old key → new key
53
- newOpts = { ...opts }
54
- for (const [oldKey, newKey] of entries)
55
- if (opts?.[oldKey] !== undefined) {
56
- newOpts[newKey] = opts[oldKey] ; delete newOpts[oldKey] }
57
- }
58
- } else { // { new-opt: default } — positional wrap
59
- const [newKey, defaultValue] = entries[0]
60
- if (opts !== undefined && !isObj)
61
- deprecation = { old: JSON.stringify(opts), new: `{ ${newKey}: ${JSON.stringify(opts)} }` }
62
- newOpts = { [newKey]: isObj ? (opts[newKey] ?? defaultValue) : (opts ?? defaultValue) }
63
- }
64
- }
65
- const aliasContext = Object.create(context)
66
- Object.defineProperty(aliasContext, 'options',
67
- { value: [newOpts], writable: true, configurable: true, enumerable: true })
68
- const visitors = { ...originals[targetName].create(aliasContext) }
69
- if (deprecation) {
70
- const ruleProgram = visitors.Program
71
- visitors.Program = function(node) {
72
- context.report({ node, messageId: 'deprecatedOption', data: deprecation })
73
- if (ruleProgram) ruleProgram.call(this, node)
74
- }
75
- }
76
- return visitors
77
- }))
78
- }
79
- }
80
- } /* eslint-enable security/detect-object-injection */