@book000/eslint-config 1.16.0 → 1.16.2

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.
Files changed (3) hide show
  1. package/index.mjs +181 -16
  2. package/package.json +1 -1
  3. package/test.mjs +162 -46
package/index.mjs CHANGED
@@ -16,12 +16,19 @@ export default tseslint.config(
16
16
  // 新規エラーが波及するリスクがあるため。新しいルールを取り込む場合は
17
17
  // このリストへ明示的に追記すること(unicorn の CHANGELOG を確認して判断する)。
18
18
  //
19
- // 生成方法: eslint-plugin-unicorn flat/recommended が定義する
20
- // ルール一覧を元に、以下を除外・調整して作成した。
19
+ // 生成方法: eslint-plugin-unicorn (現在インストールされている 71.1.0)
20
+ // flat/recommended が定義するルール一覧を元に、以下を除外・調整して作成した。
21
21
  // - deprecated ルールは対象外
22
22
  // - catch-error-name, filename-case はオプション付きで設定するため、
23
23
  // 下記の専用ブロックに記載し、ここでは省略する
24
24
  // - 意図的に off とするルール(下記コメント参照)
25
+ //
26
+ // 注意: このリストは eslint-plugin-unicorn の特定バージョン (71.1.0) 時点の
27
+ // flat/recommended を元に生成した固定スナップショットである。unicorn を
28
+ // アップデートする際は、追加・削除・改名されたルールが無いか CHANGELOG を
29
+ // 確認し、このリストへ反映すること。反映を怠ると、削除・改名されたルール ID
30
+ // を参照したままとなり、ESLint の設定検証エラーで全ユーザーがクラッシュする
31
+ // おそれがある。
25
32
  plugins: { unicorn },
26
33
  rules: {
27
34
  // 集約処理のイディオムとして正当な用途が多く、for ループへの
@@ -33,144 +40,305 @@ export default tseslint.config(
33
40
  // トップレベル await を許可する(IIFE 併用の CLI ツール等のため)
34
41
  "unicorn/prefer-top-level-await": "off",
35
42
  // 省略形を許可する (dev -> development, prod -> production などの変換をさせない)
36
- "unicorn/prevent-abbreviations": "off",
43
+ "unicorn/name-replacements": "off",
37
44
  // null を許可する
38
45
  "unicorn/no-null": "off",
39
46
  // 無駄な undefined を許可する
40
47
  "unicorn/no-useless-undefined": "off",
48
+ // クラスメンバーの順序(private before public)を強制しない。
49
+ // public-first の慣習は API の可読性として広く使われているため。
50
+ "unicorn/consistent-class-member-order": "off",
51
+ // 依存プロジェクト横断での実運用調査 (tomacheese/book000/jaoafa 各オーガニゼーション規模) の
52
+ // 結果、誤検知率が高く実利が薄いと判断したルールを以下のとおり無効化する。
53
+ //
54
+ // .then()/.catch()/.finally() チェーンを await へ書き換えるよう強制するが、
55
+ // 関数自体を async 化したくない薄いラッパー関数 (Promise を返すだけの関数) まで
56
+ // 書き換え対象になり実利が薄いため無効化する
57
+ "unicorn/prefer-await": "off",
58
+ // is/has/should 等のプレフィックスを強制するが、ドメイン語彙由来の真偽値変数名
59
+ // (enabled, visible 等) まで誤検知するため無効化する
60
+ "unicorn/consistent-boolean-name": "off",
61
+ // get/set/create 等の動詞プレフィックスを持つ関数以外の値 (変数・プロパティ等) を
62
+ // 検出するが、removeButton (削除用ボタン要素を指す変数名) のようなドメイン語彙由来の
63
+ // 命名まで誤検知するため無効化する
64
+ "unicorn/no-non-function-verb-prefix": "off",
65
+ // Number() による型強制を強制するが、parseInt 等との挙動差 (NaN の扱い等) により
66
+ // 意図せず動作が変わる危険があるため無効化する
67
+ "unicorn/prefer-number-coercion": "off",
68
+ // モジュールトップレベル変数へのキャッシュ/メモ化目的の代入という一般的な
69
+ // パターンまで誤検知するため無効化する
70
+ "unicorn/no-top-level-assignment-in-function": "off",
71
+ // \uXXXX と \u{XXXX} は等価であり、既存コードへの機械的な書き換えを強制する
72
+ // だけで実利が薄いため無効化する
73
+ "unicorn/prefer-unicode-code-point-escapes": "off",
74
+ // ネストしたループ内の break による意図的な早期脱出パターンまで誤検知するため
75
+ // 無効化する
76
+ "unicorn/no-break-in-nested-loop": "off",
77
+ // 継承・拡張を意図した static メソッド内でのクラス名参照という正当な
78
+ // パターンまで誤検知するため無効化する
79
+ "unicorn/class-reference-in-static-methods": "off",
80
+ // while (true) の先頭で break 条件を判定するコードを、条件を while() の式へ
81
+ // 移すよう強制するが、ループ内で算出した値を break 条件に使うイディオム
82
+ // (while (true) { const chunk = readNext(); if (!chunk) break; ... }) まで
83
+ // 誤検知するため無効化する
84
+ "unicorn/prefer-while-loop-condition": "off",
85
+ // location.href への代入と location.assign() はほぼ等価であり、機械的な
86
+ // 書き換えを強制するだけで実利が薄いため無効化する
87
+ "unicorn/prefer-location-assign": "off",
88
+ // 呼び出しの引数として渡された呼び出しの入れ子の深さを検出するが、
89
+ // z.array(z.object({ ... })) のような Zod 等のスキーマ定義における正当な
90
+ // 入れ子呼び出しまで誤検知するため無効化する
91
+ "unicorn/max-nested-calls": "off",
92
+ // globalThis/window/global のプロパティへの代入は、テストやモックの
93
+ // セットアップにおける標準的なイディオム (globalThis.fetch = mock 等) であり、
94
+ // 実運用コードでの誤検知率が高いため無効化する
95
+ "unicorn/no-global-object-property-assignment": "off",
41
96
  //
42
97
  // 以下、上記以外の flat/recommended 収録ルールをすべて明示的に error とする
43
98
  "unicorn/better-dom-traversing": "error",
44
99
  "unicorn/consistent-assert": "error",
45
100
  "unicorn/consistent-compound-words": "error",
101
+ "unicorn/consistent-conditional-object-spread": "error",
46
102
  "unicorn/consistent-date-clone": "error",
47
103
  "unicorn/consistent-empty-array-spread": "error",
48
104
  "unicorn/consistent-existence-index-check": "error",
105
+ "unicorn/consistent-export-decorator-position": "error",
49
106
  "unicorn/consistent-json-file-read": "error",
107
+ "unicorn/consistent-optional-chaining": "error",
50
108
  "unicorn/consistent-template-literal-escape": "error",
109
+ "unicorn/consistent-tuple-labels": "error",
110
+ "unicorn/default-export-style": "error",
51
111
  "unicorn/dom-node-dataset": "error",
52
112
  "unicorn/empty-brace-spaces": "error",
53
113
  "unicorn/error-message": "error",
54
114
  "unicorn/escape-case": "error",
55
115
  "unicorn/expiring-todo-comments": "error",
56
116
  "unicorn/explicit-length-check": "error",
117
+ "unicorn/explicit-timer-delay": "error",
57
118
  "unicorn/import-style": "error",
58
119
  "unicorn/isolated-functions": "error",
120
+ "unicorn/logical-assignment-operators": "error",
59
121
  "unicorn/new-for-builtins": "error",
60
122
  "unicorn/no-abusive-eslint-disable": "error",
61
123
  "unicorn/no-accessor-recursion": "error",
124
+ "unicorn/no-accidental-bitwise-operator": "error",
62
125
  "unicorn/no-anonymous-default-export": "error",
63
126
  "unicorn/no-array-callback-reference": "error",
127
+ "unicorn/no-array-concat-in-loop": "error",
64
128
  "unicorn/no-array-fill-with-reference-type": "error",
65
- "unicorn/no-array-for-each": "error",
66
129
  "unicorn/no-array-from-fill": "error",
67
130
  "unicorn/no-array-method-this-argument": "error",
68
131
  "unicorn/no-array-reverse": "error",
69
132
  "unicorn/no-array-sort": "error",
133
+ "unicorn/no-array-sort-for-min-max": "error",
134
+ "unicorn/no-array-splice": "error",
135
+ "unicorn/no-async-promise-finally": "error",
70
136
  "unicorn/no-await-expression-member": "error",
71
137
  "unicorn/no-await-in-promise-methods": "error",
72
138
  "unicorn/no-blob-to-file": "error",
139
+ "unicorn/no-boolean-sort-comparator": "error",
73
140
  "unicorn/no-canvas-to-image": "error",
141
+ "unicorn/no-chained-comparison": "error",
142
+ "unicorn/no-collection-bracket-access": "error",
143
+ "unicorn/no-computed-property-existence-check": "error",
74
144
  "unicorn/no-confusing-array-splice": "error",
145
+ "unicorn/no-confusing-array-with": "error",
75
146
  "unicorn/no-console-spaces": "error",
147
+ "unicorn/no-constant-zero-expression": "error",
148
+ "unicorn/no-declarations-before-early-exit": "error",
76
149
  "unicorn/no-document-cookie": "error",
150
+ "unicorn/no-double-comparison": "error",
151
+ "unicorn/no-duplicate-if-branches": "error",
152
+ "unicorn/no-duplicate-logical-operands": "error",
153
+ "unicorn/no-duplicate-loops": "error",
77
154
  "unicorn/no-duplicate-set-values": "error",
78
155
  "unicorn/no-empty-file": "error",
156
+ "unicorn/no-error-property-assignment": "error",
79
157
  "unicorn/no-exports-in-scripts": "error",
158
+ "unicorn/no-for-each": "error",
80
159
  "unicorn/no-for-loop": "error",
81
- "unicorn/no-hex-escape": "error",
82
160
  "unicorn/no-immediate-mutation": "error",
161
+ "unicorn/no-impossible-length-comparison": "error",
83
162
  "unicorn/no-incorrect-query-selector": "error",
163
+ "unicorn/no-incorrect-template-string-interpolation": "error",
84
164
  "unicorn/no-instanceof-builtins": "error",
165
+ "unicorn/no-invalid-argument-count": "error",
166
+ "unicorn/no-invalid-character-comparison": "error",
85
167
  "unicorn/no-invalid-fetch-options": "error",
86
168
  "unicorn/no-invalid-remove-event-listener": "error",
169
+ "unicorn/no-invalid-well-known-symbol-methods": "error",
87
170
  "unicorn/no-late-current-target-access": "error",
171
+ "unicorn/no-late-event-control": "error",
88
172
  "unicorn/no-lonely-if": "error",
173
+ "unicorn/no-loop-iterable-mutation": "error",
89
174
  "unicorn/no-magic-array-flat-depth": "error",
175
+ "unicorn/no-mismatched-map-key": "error",
176
+ "unicorn/no-misrefactored-assignment": "error",
90
177
  "unicorn/no-named-default": "error",
178
+ "unicorn/no-negated-array-predicate": "error",
179
+ "unicorn/no-negated-comparison": "error",
91
180
  "unicorn/no-negated-condition": "error",
92
181
  "unicorn/no-negation-in-equality-check": "error",
93
182
  "unicorn/no-nested-ternary": "error",
94
183
  "unicorn/no-new-array": "error",
95
184
  "unicorn/no-new-buffer": "error",
185
+ "unicorn/no-nonstandard-builtin-properties": "error",
96
186
  "unicorn/no-object-as-default-parameter": "error",
187
+ "unicorn/no-object-methods-with-collections": "error",
188
+ "unicorn/no-optional-chaining-on-undeclared-variable": "error",
97
189
  "unicorn/no-process-exit": "error",
190
+ "unicorn/no-redundant-comparison": "error",
191
+ "unicorn/no-return-array-push": "error",
192
+ "unicorn/no-selector-as-dom-name": "error",
98
193
  "unicorn/no-single-promise-in-promise-methods": "error",
99
194
  "unicorn/no-static-only-class": "error",
195
+ "unicorn/no-subtraction-comparison": "error",
100
196
  "unicorn/no-thenable": "error",
101
197
  "unicorn/no-this-assignment": "error",
102
198
  "unicorn/no-this-outside-of-class": "error",
199
+ "unicorn/no-top-level-side-effects": "error",
103
200
  "unicorn/no-typeof-undefined": "error",
201
+ "unicorn/no-uncalled-method": "error",
202
+ "unicorn/no-undeclared-class-members": "error",
104
203
  "unicorn/no-unnecessary-array-flat-depth": "error",
204
+ "unicorn/no-unnecessary-array-flat-map": "error",
105
205
  "unicorn/no-unnecessary-array-splice-count": "error",
106
206
  "unicorn/no-unnecessary-await": "error",
207
+ "unicorn/no-unnecessary-boolean-comparison": "error",
208
+ "unicorn/no-unnecessary-fetch-options": "error",
209
+ "unicorn/no-unnecessary-global-this": "error",
107
210
  "unicorn/no-unnecessary-nested-ternary": "error",
108
211
  "unicorn/no-unnecessary-polyfills": "error",
109
212
  "unicorn/no-unnecessary-slice-end": "error",
213
+ "unicorn/no-unnecessary-splice": "error",
110
214
  "unicorn/no-unreadable-array-destructuring": "error",
215
+ "unicorn/no-unreadable-for-of-expression": "error",
111
216
  "unicorn/no-unreadable-iife": "error",
217
+ "unicorn/no-unreadable-object-destructuring": "error",
218
+ "unicorn/no-unsafe-buffer-conversion": "error",
219
+ "unicorn/no-unsafe-promise-all-settled-values": "error",
220
+ "unicorn/no-unsafe-property-key": "error",
221
+ "unicorn/no-unsafe-string-replacement": "error",
112
222
  "unicorn/no-unused-array-method-return": "error",
223
+ "unicorn/no-useless-boolean-cast": "error",
224
+ "unicorn/no-useless-coercion": "error",
113
225
  "unicorn/no-useless-collection-argument": "error",
226
+ "unicorn/no-useless-compound-assignment": "error",
227
+ "unicorn/no-useless-concat": "error",
228
+ "unicorn/no-useless-continue": "error",
229
+ "unicorn/no-useless-delete-check": "error",
230
+ "unicorn/no-useless-else": "error",
114
231
  "unicorn/no-useless-error-capture-stack-trace": "error",
115
232
  "unicorn/no-useless-fallback-in-spread": "error",
116
233
  "unicorn/no-useless-iterator-to-array": "error",
117
234
  "unicorn/no-useless-length-check": "error",
235
+ "unicorn/no-useless-logical-operand": "error",
236
+ "unicorn/no-useless-override": "error",
118
237
  "unicorn/no-useless-promise-resolve-reject": "error",
238
+ "unicorn/no-useless-recursion": "error",
119
239
  "unicorn/no-useless-spread": "error",
120
240
  "unicorn/no-useless-switch-case": "error",
241
+ "unicorn/no-useless-template-literals": "error",
242
+ "unicorn/no-xor-as-exponentiation": "error",
121
243
  "unicorn/no-zero-fractions": "error",
122
244
  "unicorn/number-literal-case": "error",
123
245
  "unicorn/numeric-separators-style": "error",
246
+ "unicorn/operator-assignment": "error",
247
+ "unicorn/prefer-abort-signal-any": "error",
248
+ "unicorn/prefer-abort-signal-timeout": "error",
124
249
  "unicorn/prefer-add-event-listener": "error",
250
+ "unicorn/prefer-add-event-listener-options": "error",
251
+ "unicorn/prefer-aggregate-error": "error",
125
252
  "unicorn/prefer-array-find": "error",
126
253
  "unicorn/prefer-array-flat": "error",
127
254
  "unicorn/prefer-array-flat-map": "error",
255
+ "unicorn/prefer-array-from-async": "error",
256
+ "unicorn/prefer-array-from-map": "error",
257
+ "unicorn/prefer-array-from-range": "error",
128
258
  "unicorn/prefer-array-index-of": "error",
259
+ "unicorn/prefer-array-iterable-methods": "error",
129
260
  "unicorn/prefer-array-last-methods": "error",
261
+ "unicorn/prefer-array-slice": "error",
130
262
  "unicorn/prefer-array-some": "error",
131
263
  "unicorn/prefer-at": "error",
132
264
  "unicorn/prefer-bigint-literals": "error",
133
265
  "unicorn/prefer-blob-reading-methods": "error",
266
+ "unicorn/prefer-block-statement-over-iife": "error",
267
+ "unicorn/prefer-boolean-return": "error",
134
268
  "unicorn/prefer-class-fields": "error",
135
269
  "unicorn/prefer-classlist-toggle": "error",
136
270
  "unicorn/prefer-code-point": "error",
271
+ "unicorn/prefer-continue": "error",
137
272
  "unicorn/prefer-date-now": "error",
138
273
  "unicorn/prefer-default-parameters": "error",
274
+ "unicorn/prefer-direct-iteration": "error",
139
275
  "unicorn/prefer-dom-node-append": "error",
140
276
  "unicorn/prefer-dom-node-remove": "error",
277
+ "unicorn/prefer-dom-node-replace-children": "error",
141
278
  "unicorn/prefer-dom-node-text-content": "error",
279
+ "unicorn/prefer-early-return": "error",
280
+ "unicorn/prefer-else-if": "error",
142
281
  "unicorn/prefer-event-target": "error",
143
282
  "unicorn/prefer-export-from": "error",
283
+ "unicorn/prefer-flat-math-min-max": "error",
144
284
  "unicorn/prefer-get-or-insert-computed": "error",
285
+ "unicorn/prefer-global-number-constants": "error",
145
286
  "unicorn/prefer-global-this": "error",
287
+ "unicorn/prefer-group-by": "error",
288
+ "unicorn/prefer-has-check": "error",
289
+ "unicorn/prefer-hoisting-branch-code": "error",
146
290
  "unicorn/prefer-https": "error",
291
+ "unicorn/prefer-identifier-import-export-specifiers": "error",
147
292
  "unicorn/prefer-includes": "error",
148
293
  "unicorn/prefer-includes-over-repeated-comparisons": "error",
294
+ "unicorn/prefer-iterable-in-constructor": "error",
295
+ "unicorn/prefer-iterator-helpers": "error",
296
+ "unicorn/prefer-iterator-to-array": "error",
149
297
  "unicorn/prefer-iterator-to-array-at-end": "error",
150
298
  "unicorn/prefer-keyboard-event-key": "error",
151
299
  "unicorn/prefer-logical-operator-over-ternary": "error",
300
+ "unicorn/prefer-map-from-entries": "error",
152
301
  "unicorn/prefer-math-abs": "error",
302
+ "unicorn/prefer-math-constants": "error",
153
303
  "unicorn/prefer-math-min-max": "error",
154
304
  "unicorn/prefer-math-trunc": "error",
305
+ "unicorn/prefer-minimal-ternary": "error",
155
306
  "unicorn/prefer-modern-dom-apis": "error",
156
307
  "unicorn/prefer-modern-math-apis": "error",
157
308
  "unicorn/prefer-module": "error",
158
309
  "unicorn/prefer-native-coercion-functions": "error",
159
310
  "unicorn/prefer-negative-index": "error",
160
311
  "unicorn/prefer-node-protocol": "error",
312
+ "unicorn/prefer-number-is-safe-integer": "error",
161
313
  "unicorn/prefer-number-properties": "error",
314
+ "unicorn/prefer-object-define-properties": "error",
315
+ "unicorn/prefer-object-destructuring-defaults": "error",
162
316
  "unicorn/prefer-object-from-entries": "error",
317
+ "unicorn/prefer-object-iterable-methods": "error",
318
+ "unicorn/prefer-observer-apis": "error",
163
319
  "unicorn/prefer-optional-catch-binding": "error",
320
+ "unicorn/prefer-path2d": "error",
321
+ "unicorn/prefer-private-class-fields": "error",
322
+ "unicorn/prefer-promise-try": "error",
323
+ "unicorn/prefer-promise-with-resolvers": "error",
164
324
  "unicorn/prefer-prototype-methods": "error",
165
325
  "unicorn/prefer-query-selector": "error",
166
326
  "unicorn/prefer-queue-microtask": "error",
167
327
  "unicorn/prefer-reflect-apply": "error",
168
328
  "unicorn/prefer-regexp-test": "error",
169
329
  "unicorn/prefer-response-static-json": "error",
330
+ "unicorn/prefer-scoped-selector": "error",
170
331
  "unicorn/prefer-set-has": "error",
332
+ "unicorn/prefer-set-methods": "error",
171
333
  "unicorn/prefer-set-size": "error",
172
334
  "unicorn/prefer-simple-condition-first": "error",
335
+ "unicorn/prefer-simple-sort-comparator": "error",
336
+ "unicorn/prefer-simplified-conditions": "error",
337
+ "unicorn/prefer-single-array-predicate": "error",
173
338
  "unicorn/prefer-single-call": "error",
339
+ "unicorn/prefer-single-object-destructuring": "error",
340
+ "unicorn/prefer-single-replace": "error",
341
+ "unicorn/prefer-smaller-scope": "error",
174
342
  "unicorn/prefer-split-limit": "error",
175
343
  "unicorn/prefer-spread": "error",
176
344
  "unicorn/prefer-string-match-all": "error",
@@ -184,14 +352,22 @@ export default tseslint.config(
184
352
  "unicorn/prefer-structured-clone": "error",
185
353
  "unicorn/prefer-switch": "error",
186
354
  "unicorn/prefer-ternary": "error",
355
+ "unicorn/prefer-toggle-attribute": "error",
187
356
  "unicorn/prefer-type-error": "error",
357
+ "unicorn/prefer-type-literal-last": "error",
358
+ "unicorn/prefer-unary-minus": "error",
359
+ "unicorn/prefer-url-can-parse": "error",
360
+ "unicorn/prefer-url-href": "error",
361
+ "unicorn/prefer-url-search-parameters": "error",
188
362
  "unicorn/relative-url-style": "error",
189
363
  "unicorn/require-array-join-separator": "error",
364
+ "unicorn/require-array-sort-compare": "error",
190
365
  "unicorn/require-css-escape": "error",
191
366
  "unicorn/require-module-attributes": "error",
192
367
  "unicorn/require-module-specifiers": "error",
193
368
  "unicorn/require-number-to-fixed-digits-argument": "error",
194
369
  "unicorn/require-passive-events": "error",
370
+ "unicorn/require-proxy-trap-boolean-return": "error",
195
371
  "unicorn/switch-case-braces": "error",
196
372
  "unicorn/switch-case-break-position": "error",
197
373
  "unicorn/template-indent": "error",
@@ -254,17 +430,6 @@ export default tseslint.config(
254
430
  // 変数などの未定義使用について、TypeScript準拠とする
255
431
  "no-use-before-define": "off",
256
432
  "@typescript-eslint/no-use-before-define": "error",
257
- // トップレベルのawaitを許可する
258
- "unicorn/prefer-top-level-await": "off",
259
- // クラスメンバーの順序(private before public)を強制しない。
260
- // public-first の慣習は API の可読性として広く使われているため。
261
- "unicorn/consistent-class-member-order": "off",
262
- // 省略形を許可する (dev -> development, prod -> productionなどの変換をさせない)
263
- "unicorn/prevent-abbreviations": "off",
264
- // nullを許可する
265
- "unicorn/no-null": "off",
266
- // 無駄なundefinedを許可する
267
- "unicorn/no-useless-undefined": "off",
268
433
  },
269
434
  },
270
435
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@book000/eslint-config",
3
- "version": "1.16.0",
3
+ "version": "1.16.2",
4
4
  "description": "ESLint config",
5
5
  "keywords": [
6
6
  "eslint-config"
package/test.mjs CHANGED
@@ -1,7 +1,10 @@
1
1
  // Node.jsスクリプトでESLint flat config(index.mjs)の動作を検証するサンプル
2
- import { exec } from "child_process";
2
+ import { execFile } from "child_process";
3
3
  import fs from "fs";
4
4
  import path from "path";
5
+ import { promisify } from "util";
6
+
7
+ const execFileAsync = promisify(execFile);
5
8
 
6
9
  async function main() {
7
10
  const testCases = [
@@ -12,10 +15,10 @@ async function main() {
12
15
  rules: ["unicorn/prefer-top-level-await"],
13
16
  },
14
17
  {
15
- name: "prevent-abbreviations: dev, prodなど省略形はOK",
18
+ name: "name-replacements: dev, prodなど省略形はOK",
16
19
  code: "const dev = true; const prod = false;",
17
20
  shouldError: false,
18
- rules: ["unicorn/prevent-abbreviations"],
21
+ rules: ["unicorn/name-replacements"],
19
22
  },
20
23
  {
21
24
  name: "no-null: nullの使用はOK",
@@ -218,6 +221,78 @@ async function main() {
218
221
  shouldError: false,
219
222
  rules: ["unicorn/consistent-class-member-order"],
220
223
  },
224
+ {
225
+ name: "prefer-await: .then() チェーンを使う非asyncな薄いラッパー関数はOK",
226
+ code: "function getValue(): Promise<number> { return Promise.resolve(1).then((value) => value); }",
227
+ shouldError: false,
228
+ rules: ["unicorn/prefer-await"],
229
+ },
230
+ {
231
+ name: "consistent-boolean-name: is/has等のプレフィックスがない真偽値変数名はOK",
232
+ code: "const enabled: boolean = true;",
233
+ shouldError: false,
234
+ rules: ["unicorn/consistent-boolean-name"],
235
+ },
236
+ {
237
+ name: "no-non-function-verb-prefix: 動詞プレフィックスを持つ非関数の変数名はOK",
238
+ code: "const removeButton = 1;",
239
+ shouldError: false,
240
+ rules: ["unicorn/no-non-function-verb-prefix"],
241
+ },
242
+ {
243
+ name: "prefer-number-coercion: parseInt による数値変換はOK",
244
+ code: "const n = parseInt('1', 10);",
245
+ shouldError: false,
246
+ rules: ["unicorn/prefer-number-coercion"],
247
+ },
248
+ {
249
+ name: "no-top-level-assignment-in-function: トップレベル変数へのキャッシュ代入はOK",
250
+ code: "let cache: number | undefined; function setCache(): void { cache = 1; }",
251
+ shouldError: false,
252
+ rules: ["unicorn/no-top-level-assignment-in-function"],
253
+ },
254
+ {
255
+ name: "prefer-unicode-code-point-escapes: \\uXXXX形式のエスケープはOK",
256
+ code: "const s = '\\u00e9';",
257
+ shouldError: false,
258
+ rules: ["unicorn/prefer-unicode-code-point-escapes"],
259
+ },
260
+ {
261
+ name: "no-break-in-nested-loop: ネストしたループ内のbreakによる早期脱出はOK",
262
+ code: "for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { if (j === 1) break; } }",
263
+ shouldError: false,
264
+ rules: ["unicorn/no-break-in-nested-loop"],
265
+ },
266
+ {
267
+ name: "class-reference-in-static-methods: staticメソッド内での非呼び出し位置のクラス名参照はOK",
268
+ code: "class Foo { static bar = 1; static create(): number { return Foo.bar; } }",
269
+ shouldError: false,
270
+ rules: ["unicorn/class-reference-in-static-methods"],
271
+ },
272
+ {
273
+ name: "prefer-while-loop-condition: while(true) + 内部breakによる終了判定はOK",
274
+ code: "let i = 0; while (true) { if (i >= 5) break; i++; }",
275
+ shouldError: false,
276
+ rules: ["unicorn/prefer-while-loop-condition"],
277
+ },
278
+ {
279
+ name: "prefer-location-assign: location.hrefへの代入はOK",
280
+ code: "location.href = 'https://example.com';",
281
+ shouldError: false,
282
+ rules: ["unicorn/prefer-location-assign"],
283
+ },
284
+ {
285
+ name: "max-nested-calls: スキーマ定義等のメソッドチェーンによる深いネストはOK",
286
+ code: "function a(x: number) { return b(x); } function b(x: number) { return c(x); } function c(x: number) { return d(x); } function d(x: number) { return x; } const result = a(b(c(d(1))));",
287
+ shouldError: false,
288
+ rules: ["unicorn/max-nested-calls"],
289
+ },
290
+ {
291
+ name: "no-global-object-property-assignment: globalThisプロパティへの代入(テストのモック等)はOK",
292
+ code: "globalThis.fetch = (() => Promise.resolve(new Response())) as typeof fetch;",
293
+ shouldError: false,
294
+ rules: ["unicorn/no-global-object-property-assignment"],
295
+ },
221
296
  {
222
297
  name: "filename-case: checkDirectories: false のため kebab-case でないディレクトリ名もエラーにならない(OK)",
223
298
  code: "export const a = 1;",
@@ -276,57 +351,98 @@ async function main() {
276
351
  const flatConfigPath = path.join(process.cwd(), "eslint.config.mjs");
277
352
  fs.copyFileSync(path.join(process.cwd(), "index.mjs"), flatConfigPath);
278
353
 
279
- // 並列実行用のPromise配列
280
- const promises = testCases.map((testCase, i) => {
281
- return new Promise((resolve) => {
282
- const { name, code, shouldError, rules, dir } = testCase;
283
- const kebabName = `test-${i}.ts`;
284
- const targetDir = dir ? path.join(srcDir, dir) : tmpDir;
285
- const tmpFilePath = path.join(targetDir, kebabName);
286
- fs.writeFileSync(tmpFilePath, code);
287
- exec(
288
- `npx eslint --no-cache --ext .ts ${tmpFilePath}`,
289
- (error, stdout, stderr) => {
290
- const output = (stdout || "") + (stderr || "");
291
- // テスト項目ごとに対象ルールのみを判定
292
- const errorLines = output
293
- .split("\n")
294
- .filter((line) => line.match(/error/));
295
- let errorCount = 0;
296
- let ignoredErrors = [];
297
- const relevantErrors = [];
298
- for (const line of errorLines) {
299
- const ruleMatch = line.match(/\s([\w@\-/]+)$/);
300
- const rule = ruleMatch ? ruleMatch[1] : null;
301
- if (rule && rules.includes(rule)) {
302
- errorCount = 1;
303
- relevantErrors.push(line);
304
- } else {
305
- ignoredErrors.push(line);
306
- }
307
- }
308
- fs.unlinkSync(tmpFilePath);
309
- resolve({
310
- name,
311
- shouldError,
312
- errorCount,
313
- output,
314
- relevantErrors,
315
- ignoredErrors,
316
- });
317
- }
318
- );
319
- });
354
+ // テスト用一時ファイルをすべて書き出す (プロセス起動は後で 1 回だけ行う)
355
+ const tmpFilePaths = testCases.map((testCase, i) => {
356
+ const { code, dir } = testCase;
357
+ const kebabName = `test-${i}.ts`;
358
+ const targetDir = dir ? path.join(srcDir, dir) : tmpDir;
359
+ const tmpFilePath = path.join(targetDir, kebabName);
360
+ fs.writeFileSync(tmpFilePath, code);
361
+ return tmpFilePath;
320
362
  });
321
363
 
322
- const results = await Promise.all(promises);
364
+ // 全テストケースを 1 回の eslint 起動でまとめて検証する。
365
+ // 以前はテストケースごとに `npx eslint` を並列起動していたため、
366
+ // type-aware (typescript-eslint) なパーサ初期化がテストケース数分同時に走り、
367
+ // メモリ枯渇・OOM killer の引き金になっていた。
368
+ // 1 プロセス・1 回の TypeScript プログラム初期化にまとめることで、
369
+ // プロセス数をテストケース数から 1 に減らし、メモリ使用量を大幅に下げる。
370
+ const eslintBinPath = path.join(
371
+ process.cwd(),
372
+ "node_modules",
373
+ "eslint",
374
+ "bin",
375
+ "eslint.js"
376
+ );
377
+ let jsonOutput = "";
378
+ try {
379
+ const { stdout } = await execFileAsync(
380
+ process.execPath,
381
+ [
382
+ eslintBinPath,
383
+ "--no-cache",
384
+ "--ext",
385
+ ".ts",
386
+ "--format",
387
+ "json",
388
+ ...tmpFilePaths,
389
+ ],
390
+ { maxBuffer: 1024 * 1024 * 50 }
391
+ );
392
+ jsonOutput = stdout;
393
+ } catch (error) {
394
+ // eslint はテストケースに期待どおりのエラーがあると非ゼロの exit code を返すが、
395
+ // これはテストとして期待される挙動なので、JSON 形式の stdout が取れていれば
396
+ // 正常系として扱う。設定エラー等で stdout に JSON が出力されない場合は
397
+ // ここでは判別せず、後続の JSON.parse で例外として検出させる。
398
+ if (error.stdout) {
399
+ jsonOutput = error.stdout;
400
+ } else {
401
+ throw error;
402
+ }
403
+ }
404
+
405
+ const eslintResults = JSON.parse(jsonOutput);
406
+ const resultsByFilePath = new Map(
407
+ eslintResults.map((result) => [result.filePath, result])
408
+ );
409
+
410
+ const results = testCases.map((testCase, i) => {
411
+ const { name, shouldError, rules } = testCase;
412
+ const tmpFilePath = tmpFilePaths[i];
413
+ const fileResult = resultsByFilePath.get(tmpFilePath);
414
+ if (!fileResult) {
415
+ // 対象ファイルが eslint の結果に存在しない場合、空メッセージとして
416
+ // 握りつぶすと「エラー 0 件 = OK」と誤ってパスしてしまう。
417
+ // ファイルが実際には lint されていないことを示すため、例外として検出する。
418
+ throw new Error(
419
+ `ESLint did not return a result for temp file: ${tmpFilePath}`
420
+ );
421
+ }
422
+
423
+ let errorCount = 0;
424
+ const relevantErrors = [];
425
+ const ignoredErrors = [];
426
+ for (const message of fileResult.messages) {
427
+ const severityLabel = message.severity === 2 ? "error" : "warning";
428
+ const line = ` ${message.line}:${message.column} ${severityLabel} ${message.message} ${message.ruleId ?? ""}`;
429
+ if (message.ruleId && rules.includes(message.ruleId)) {
430
+ errorCount = 1;
431
+ relevantErrors.push(line);
432
+ } else {
433
+ ignoredErrors.push(line);
434
+ }
435
+ }
436
+
437
+ fs.unlinkSync(tmpFilePath);
438
+ return { name, shouldError, errorCount, relevantErrors, ignoredErrors };
439
+ });
323
440
  let pass = 0,
324
441
  fail = 0;
325
442
  for (const {
326
443
  name,
327
444
  shouldError,
328
445
  errorCount,
329
- output,
330
446
  relevantErrors,
331
447
  ignoredErrors,
332
448
  } of results) {