eslint-config-typed 2.0.0 → 2.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.
Files changed (56) hide show
  1. package/README.md +62 -15
  2. package/dist/configs/jest.d.mts.map +1 -1
  3. package/dist/configs/preact.d.mts.map +1 -1
  4. package/dist/configs/preact.mjs.map +1 -1
  5. package/dist/configs/react.mjs.map +1 -1
  6. package/dist/configs/testing-library.d.mts.map +1 -1
  7. package/dist/configs/typescript-without-rules.d.mts.map +1 -1
  8. package/dist/configs/typescript-without-rules.mjs +10 -1
  9. package/dist/configs/typescript-without-rules.mjs.map +1 -1
  10. package/dist/configs/typescript.d.mts.map +1 -1
  11. package/dist/configs/typescript.mjs.map +1 -1
  12. package/dist/configs/vitest.d.mts.map +1 -1
  13. package/dist/entry-point.d.mts +1 -1
  14. package/dist/entry-point.d.mts.map +1 -1
  15. package/dist/entry-point.mjs +1 -1
  16. package/dist/index.mjs +1 -1
  17. package/dist/rules/eslint-import-rules.d.mts.map +1 -1
  18. package/dist/rules/eslint-import-rules.mjs +1 -0
  19. package/dist/rules/eslint-import-rules.mjs.map +1 -1
  20. package/dist/rules/eslint-plugin-rules.d.mts +1 -0
  21. package/dist/rules/eslint-plugin-rules.d.mts.map +1 -1
  22. package/dist/rules/eslint-plugin-rules.mjs +1 -0
  23. package/dist/rules/eslint-plugin-rules.mjs.map +1 -1
  24. package/dist/rules/eslint-react-rules.mjs +1 -1
  25. package/dist/rules/eslint-rules.d.mts +67 -3
  26. package/dist/rules/eslint-rules.d.mts.map +1 -1
  27. package/dist/rules/eslint-rules.mjs +182 -36
  28. package/dist/rules/eslint-rules.mjs.map +1 -1
  29. package/dist/rules/eslint-unicorn-rules.d.mts +4 -1
  30. package/dist/rules/eslint-unicorn-rules.d.mts.map +1 -1
  31. package/dist/rules/eslint-unicorn-rules.mjs +4 -1
  32. package/dist/rules/eslint-unicorn-rules.mjs.map +1 -1
  33. package/dist/rules/index.mjs +1 -1
  34. package/dist/types/rules/eslint-plugin-rules.d.mts +17 -0
  35. package/dist/types/rules/eslint-plugin-rules.d.mts.map +1 -1
  36. package/dist/types/rules/eslint-react-hooks-rules.d.mts +2 -1
  37. package/dist/types/rules/eslint-react-hooks-rules.d.mts.map +1 -1
  38. package/dist/types/rules/eslint-unicorn-rules.d.mts +219 -142
  39. package/dist/types/rules/eslint-unicorn-rules.d.mts.map +1 -1
  40. package/package.json +11 -8
  41. package/src/configs/jest.mts +1 -1
  42. package/src/configs/preact.mts +36 -35
  43. package/src/configs/react.mts +1 -1
  44. package/src/configs/testing-library.mts +1 -1
  45. package/src/configs/typescript-without-rules.mts +10 -1
  46. package/src/configs/typescript.mts +74 -72
  47. package/src/configs/vitest.mts +1 -1
  48. package/src/entry-point.mts +2 -0
  49. package/src/rules/eslint-import-rules.mts +2 -0
  50. package/src/rules/eslint-plugin-rules.mts +1 -0
  51. package/src/rules/eslint-react-rules.mts +1 -1
  52. package/src/rules/eslint-rules.mts +216 -38
  53. package/src/rules/eslint-unicorn-rules.mts +5 -1
  54. package/src/types/rules/eslint-plugin-rules.mts +18 -0
  55. package/src/types/rules/eslint-react-hooks-rules.mts +2 -1
  56. package/src/types/rules/eslint-unicorn-rules.mts +226 -142
@@ -84,6 +84,221 @@ export const restrictedGlobalsForBrowser = [
84
84
  },
85
85
  ] as const satisfies EslintRulesOption['no-restricted-globals'];
86
86
 
87
+ export const restrictedSyntax = [
88
+ {
89
+ // ban "in" operator
90
+ selector: "BinaryExpression[operator='in']",
91
+ message: 'use "Object.hasOwn" instead.',
92
+ },
93
+ {
94
+ // ban Object.prototype.hasOwnProperty.call
95
+ selector:
96
+ "MemberExpression[object.object.object.name='Object'][object.object.property.name='prototype'][object.property.name='hasOwnProperty'][property.name='call']",
97
+ message: 'use "Object.hasOwn" instead.',
98
+ },
99
+ {
100
+ // ban "new Array" expression
101
+ selector: "NewExpression[callee.name='Array']",
102
+ message: 'use Array.from instead.',
103
+ },
104
+
105
+ // Covered by @typescript-eslint/consistent-type-assertions or total-functions/no-unsafe-type-assertion
106
+ // {
107
+ // // ban "as"
108
+ // selector: "TSAsExpression[typeAnnotation.typeName.name!='const']",
109
+ // message: "Don't use `as`.",
110
+ // },
111
+
112
+ // TODO
113
+ // {
114
+ // selector:
115
+ // "Identifier[name='draft'][parent.parent.callee.name!='produce'][parent.parent.parent.parent.parent.parent.callee.name!='produce']",
116
+ // message:
117
+ // "Don't use the identifier name `draft` except in immer produce function.",
118
+ // },
119
+ ] as const satisfies EslintRulesOption['no-restricted-syntax'];
120
+
121
+ export const restrictedSyntaxForReact = {
122
+ // [These rules recommends React component style like below]
123
+ //
124
+ // import * as React from "react";
125
+ //
126
+ // type Props = Readonly<{
127
+ // numList: readonly number[];
128
+ // }>;
129
+ //
130
+ // OK
131
+ // export const Ok = React.memo<Props>((props) => {
132
+ // const sum = React.useMemo(
133
+ // () => props.numList.reduce((a, b) => a + b, 0),
134
+ // [props.numList],
135
+ // );
136
+ //
137
+ // return <div>{sum}</div>;
138
+ // });
139
+ //
140
+ // export const NoReturnStatementWithSpread = React.memo<Props>(({ numList }) => <div>{numList.length}</div>); // OK
141
+ //
142
+ // export const NoReturnStatementWithoutSpread = React.memo<Props>((props) => <div>{props.numList.length}</div>); // OK
143
+
144
+ // https://typescript-eslint.io/play/#ts=5.9.3&showAST=es&fileType=.tsx&code=JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcARFDvmQNwCwAUKJLIiugDYQAmaAFnCIlyXXqj61GjGAE8wWOAAViYdAF5MObhAB2HGQB4A3ozhwdAVxAAZYKhgAuOJWTa9M81YBGWKAG0AXXoGAF8APmDGAHoouAB5AGlGLAAPFnhcXXt4gGs4DWw8GAA6ECwQCANlCFUwgAo6sBVUAEp8sLgTBjNMnWzUK3zNIuKLVCwAWXKIOtMzODq2tQ6mmtRiyxs7EspuC1wsBuQAGjgvJY7kOABqM9OABhbjubM-VdUNq1t7AOfuuBakX%2BlBgFigOjgBm4wAAbmEjAMQCEDFFoXDgiFAVIGDF4kkGKl0nBetkAHIQbCg8EAZRgyBg5SwOhgAHVgDA%2BNSmlohoV8KVplVmvU6kZPFtspj2pC0fDNt8ShwmQBzDnI1GwsJYhjRWKJZJpaAZLLwcmUsE6Wn0xnMtkciAWGBclzcXlUEplCpCtYi96taVQzVGP2fCWKlVqlGy7W6uCkgDiBqJJPgAEEoMrUzodBA6QzXQV3QKvdVag0-U5S-7lp05im4Ii3SMxpNprN-mZFtKQ-LtsVdvtDnUTmcLigbnc4I8-vM4G9mqGFb85jHgVgqRDA3CEVZ1bKMavcQmk0biSa4OnM9nc9buKnUABJPTAHQKQsjT2VKu%2B5pOMW9%2BwnBdXR9HFHx-ACOApRrLoenPRt335FspgqdtZy7Gsey%2BPsBwOI5TnOaUrluLwHieF45ywsNl3%2BVczBBC0ZSDRE901A8gSPRMCUNVh60vLMczzLA70fZ9XztPgABU5CwbACF8JkDibflP29MtGl-YY3H0YxxQVICtBAjxNnAwIoK1aVYLPPp4AQ4YkPGFCZgojCVgXACdmEwd8NHIiJ1IqdyI7Sj3Own4ZwBIF6PXRit3hFiozYxhMQ42Jj245NzyciSHRgaT5EvKwmUceySicgBRFJc2AXAAGESEgV9mQMDS1krZoxz5EoAClqQADWKcqlTKZkOkQj021a1Qxys%2Bs7K60ZHLbFyxyohV%2By8vDhwIsdiMnacKPnNZF22GizDo5wYvBJjtwSjV0WSw80q4wlT3rJyBJvfN7yzDxxuLSpkB0GQfzWGa63gwZ-uQ5bgtcuA1pwzahxHQiaz2gKDuCo6Pg8s7IuxaKNxu%2BLd0Sh7QieuMXp440bLgD7ryEkSnw4F831KgHdI8gzXCMsDfDM8Jyw6yyIfp%2Baixh1CVu7UKww2vYttR3b-LIiLXkR8KVyiy7ibinckXJiJHtS6mT14rLpk%2B5n71Z9mJPy2SsHkygdCU-7VMKbTDH-MKSuA9wBYg8zQemsX-jmqHOel5y4dW%2BX1twlGdr8kj1cOrWYHxi6GOug27v3U3sU4i26eyS9nS0YTlImkthQaP2wyg8HI8h0hoaWmX4%2BlDzFe87bfPRtXAo1uc8Yi3Ors3WVDdYimUpL57GHrSSIHXjfN637eN%2BsXRlXq8BdGK0lkDKWuue-EWwYjuCJejhbY7Q%2BZ4az-vldT4f09HzPE9OyfdZ5xnsxMm90TaUyBEAA&eslintrc=N4KABGBEBOCuA2BTAzpAXGUEKQHYHsBaaFAF2gEsBjUxAE0OQE9dSBDAD3TAG1xsciaNHzRIAGn4CsA7JGSIkNUd0gBJALYAHUaQAiiKvDbQ2pCvlw9k%2BWNCqIAdADc28WIgC8AchJsa3gC61lqGFABmFELIjgAMjqRMoQCEPpo60KQAcmwaKFr%2BiADKoVQRUdBBElKykHnIyGwA5oiqAEqI-qRgyAAWtvB0YABGiGAU2rr0YGzIYAAGExndAFQzcx1dYOEiGmC%2BnQHzjpA1EAC%2BkrKYZ3IKSqQqGOp0iKzlQjy4uYip3psBYIFEisBJJLzedK6HL1AoOEphSJCII8YFvUiONGgxKhNKTTIGIwmMwWXAorEYimOGx2BwuNweHx%2BQHVa44eqNFqqAAqvTG31hhTAArGSymQx2%2BD2By63jAGlgyG6o32ANI3hOt0uIB11xk13kikMjzEz25RQAirA3B86DDEDwkOEMSKfGqUZQmr0XT8fAAxADCVSubLqKE5rWeAFUFGA1Y48hp8AAeAAKIi0yAAfAAKHNaDPIACUYE8WcwYEcVbA5xLFFwSs6dE1121etuOHuxqeUHNVptSLtP0diGdjld-0O6uCnu94993j9sFwNFJAalOlw6ODHagHOakagMbG8cTKfT%2BEzufzhZLZYrVccNbrDdobGbp1bkl1sn1tS7yimlAACyiAaKM0AAKIcAW4akjw%2BDDAAVsa855G6U7khmQiJGhEJnuSJjomCoQAILQE0sB5KwyCeMuryRFudCBKyob7lyzynmB%2BA9P0CBDL0bDOGMOJjMCPy0NACzeBembeMcn6yG2v67oaDw9pAoHgUI0GwQ08GIShNB4RhspAthmRMCZ3gEUCRHYuC5GUdRpDIKk9GjvW9B2SCGKiU5VHojEol-Oa3LgqmxJ5JJaivmw7wkpYO5snu4YHu0U4JtxvEDAJQkieCYDidFQjSbJyDyS2SkhtIqkASaqhaRBukkPplgIchqETu65mXjhVkTrZqL2X5jkUYFNHubgDFecxw2%2BSRiABS5wXgj4YURVFiAxXFCXmElPnEf540rZiUUxPEIXrUU4WhB04RCG8DjJWxaUcVAXFJjl-FgIJwlgKJRVbZJZWFpVikCMptUpWp3ZAZpYHNTBrXIAZnXGd1mG9aElnWUNVLHc5QVTTNTGHQ5ZEnUFi3XbdiCRaYJXQLFSrxeYiVkuTo2U0TNFnYzF0094G13aOj0roghELYTE2ufzuSC6J9p4X85UvQa7GHpAn08X0uW-flAOFcV22lfMMlgwpWrfrcf4CLDgE8jdjm4AQ7D7VYBNrZCrzvEilRc-zR3e%2BRIgAO5LiuHstXBB3zcRVJe7i3gBm48Ax21nPx6CicjY4VBp4gTiGV1C49dnlJ51SBfwEgTgFn1uODdx6u1JrGVaMYDgLDreYN5mGDlXe5bAJW1a1vMYBhxQpC9D3mVnmmhbXv3xaliPY9PhPVWQzb7Yw-VGlqL75j%2B18Px-KvUvB8nof4BHy6rpYGeo3HVKIMjse4Kk5AeIH1eF2LujH06FJxmQrkHUENc66YgsrhZuSZW723bmaPkMwqasGFD8MA%2BBwjoPDtsR%2BHsgYNGmI8OMC9sp6x%2BiqbwV8d7YChtgO2dwjSO2eAAeWAZFUgklPZ51Ej4O%2BD8o6khfqSa%2BoIP56Vft-Twv9ED-zztAoujgS4YzLljCBADa6qP7v1PGLcWI1Tbm9LWvIxKFhwXgtgYAqAbksOiOxlh2D1nrE0GYYASCkDsLgHo7swJOKImAeAEwZ5kJ4rY1wlA2DDCQFgvIQxzZXytl%2BHUts6psIas8Y%2B6IPjQHPuhAA9AAPUcMAAALAAJnEOcAAJEUwOgjvAADUTAUFiUgQkxhTAmkkRiNx3MISp1ruIt%2BedBn50AWo4B1ly5UkmSo%2BucCBoLlsqxDWZieRoPsZMLcmCRQhLeE0We31BgjDGEgBoANBJ%2BOqU%2BdcDYKCvCkiQMOlBaDrAWPY18YB1x7KcZ4ChXQspJiXpebMfdbzrwrLWAA3GAaRugKz-M3EEuYRQpSIFRY4zB5w4UQ0YXvFSB8skaSajpT%2BmcOpGRARCeZKzrKKkQOkIQJJhIAAl4p0CQEguQKCQL4H%2BvGZlRQAkAx4jPOY8UHBKlEM4gFmD6yNnfAwi4JiIAsM7GS%2BGIslqu3wO7eCudpbexGenKlsj%2BmQIxEsmZtK5laJNcRO1%2Bim4LmZVpfAfL2RbNQWMaJHS4kFVCGAbkZyhgqnim7Mw0xZjzxBZ67iyZuRZknvK%2BYPylRgGcBgcNQKRUKC9TmR8RZUlKRqIEfg5wQDnCAA&tsconfig=N4KABGBEDGD2C2AHAlgGwKYCcDyiAuysAdgM6QBcYoEEkJemy0eFYDArugDTg2RGwAqkWgALdNADW6ACYBJIjPQAPWQEFo0dCTKUO6XgF8QhoA&tokens=false
145
+
146
+ // TODO: Create a separate plugin for these rules
147
+
148
+ /* Restrict import style of React */
149
+ importStyle: [
150
+ {
151
+ selector:
152
+ "ImportDeclaration[source.value='react'][specifiers.0.type!='ImportNamespaceSpecifier']",
153
+ message: "React should be imported as `import * as React from 'react'`.",
154
+ // NOTE: React の import 方法を `import * as React from 'react'` と namespace import のみに限定するルール。
155
+ // import を1回で済ませられて便利なのと、 React.* に対する以降のルールを書きやすくするため。
156
+ // tree-shaking に悪影響は無い。
157
+ },
158
+ {
159
+ selector:
160
+ "Identifier[name!='React'][parent.type='ImportNamespaceSpecifier'][parent.parent.type='ImportDeclaration'][parent.parent.source.value='react']",
161
+ message: "The namespace name imported from 'react' must be 'React'.",
162
+ },
163
+ ],
164
+
165
+ /* Restrict React component definition style */
166
+
167
+ componentVarTypeAnnotation: [
168
+ {
169
+ // Ban "React.FC"
170
+ selector: "TSQualifiedName[left.name='React'][right.name='FC']",
171
+ message: 'Use React.memo<Props>((props) => { ... }) instead.',
172
+ // NOTE: React.FC による型注釈があれば React.memo を使うように促すルール。
173
+ // React.FC で型注釈されていない React.memo 化されていないコンポーネントは別途検出する必要がある。
174
+ },
175
+ {
176
+ // Ban "React.FunctionComponent"
177
+ selector:
178
+ "TSQualifiedName[left.name='React'][right.name='FunctionComponent']",
179
+ message: 'Use React.memo<Props>((props) => { ... }) instead.',
180
+ },
181
+ ],
182
+
183
+ reactMemoTypeParam: [
184
+ // NOTE: React.memo の型引数を `Props` に限定する。
185
+ // これは 1ファイル1コンポーネントの強制も意味する。
186
+ // 前提: component が React.memo でラップされていること。
187
+ {
188
+ selector:
189
+ // Detects `React.memo(...)`
190
+ "MemberExpression[object.name='React'][property.name='memo'][parent.typeArguments=undefined]",
191
+ message: "React.memo should have type parameter `'Props'`.",
192
+ },
193
+ {
194
+ selector:
195
+ // Detects `React.memo<NotProps>(...)`
196
+ "MemberExpression[object.name='React'][property.name='memo'][parent.typeArguments!=undefined][parent.typeArguments.type!='TSTypeParameterInstantiation']",
197
+ message: "React.memo should have type parameter `'Props'`.",
198
+ },
199
+ {
200
+ selector:
201
+ // Detects `React.memo<{ prop1: number }>(...)`
202
+ "MemberExpression[object.name='React'][property.name='memo'][parent.typeArguments!=undefined][parent.typeArguments.type='TSTypeParameterInstantiation'][parent.typeArguments.params.0.type!='TSTypeReference']",
203
+ message: "React.memo should have type parameter `'Props'`.",
204
+ },
205
+ {
206
+ selector:
207
+ // Detects `React.memo<NotProps>(...)`, `React.memo<Readonly<{ prop1: number }>>(...)`
208
+ "MemberExpression[object.name='React'][property.name='memo'][parent.typeArguments!=undefined][parent.typeArguments.type='TSTypeParameterInstantiation'][parent.typeArguments.params.0.type='TSTypeReference'][parent.typeArguments.params.0.typeName.name!='Props']",
209
+ message: "React.memo should have type parameter `'Props'`.",
210
+ },
211
+ ],
212
+
213
+ propsTypeAnnotationStyle: [
214
+ {
215
+ // Restrict Props type annotation style for React.memo
216
+ selector:
217
+ "TSTypeAnnotation[parent.type='Identifier'][parent.parent.type='ArrowFunctionExpression'][parent.parent.parent.type='CallExpression'][parent.parent.parent.callee.object.name='React'][parent.parent.parent.callee.property.name='memo']",
218
+ message:
219
+ 'Replace `React.memo((props: Props) => { ... })` with `React.memo<Props>((props) => { ... })`.',
220
+ // 前提: Arrow function の使用が強制されていること。
221
+ },
222
+ ],
223
+
224
+ /* Restrict props argument name to be "props" */
225
+ reactMemoPropsArgumentName: [
226
+ {
227
+ // Restrict props argument name to be "props"
228
+ selector:
229
+ "Identifier[name!='props'][parent.type='ArrowFunctionExpression'][parent.expression!=true][parent.parent.callee.object.name='React'][parent.parent.callee.property.name='memo']",
230
+ message:
231
+ "The argument name of arrow function passed to React.memo should be 'props'.",
232
+ // NOTE: component props を "props" という名前に限定する。
233
+ // 前提: component が React.memo でラップされていること。Arrow function の使用が強制されていること。
234
+ },
235
+ {
236
+ // Restrict props argument to be "props" variable (object destructuring is not allowed)
237
+ selector:
238
+ // Detect `React.memo<Props>(({ prop1, prop2 }) => { ... })`
239
+ "ObjectPattern[parent.type='ArrowFunctionExpression'][parent.expression!=true][parent.parent.callee.object.name='React'][parent.parent.callee.property.name='memo']",
240
+ message:
241
+ "The props of a component containing a return statement are limited to a variable named `'props'`.",
242
+ // NOTE: return 文を含む component の props を "props" という名前の変数に限定する。
243
+ // 前提: component が React.memo でラップされていること。Arrow function の使用が強制されていること。
244
+ },
245
+ ],
246
+
247
+ componentName: {
248
+ maxLength: (maxLength: number = 42) => [
249
+ {
250
+ // Limit the length of component names ${maxLength} characters
251
+ selector: `Identifier[name=/^.{${maxLength},}$/][parent.type='VariableDeclarator'][parent.init.type='CallExpression'][parent.init.callee.object.name='React'][parent.init.callee.property.name='memo']`,
252
+ message: `The component name length should be less than ${maxLength}. Consider rewrite as \`const Component = React.memo<Props>((props) => { }); export { Component as SomeComponent };\`.`,
253
+ // NOTE:
254
+ // `const Name = React.memo<Props>((props) => {`
255
+ // が1行に収まるようにするためのルール。1行に収まらないとインデントが増えてコンポーネント実装の可読性が下がりやすくなるため。
256
+ // component props の型名や引数名の制約と併せて、 prettier のデフォルト print-width = 80 で export を省いた場合の最大長としてデフォルト値 42 を設定している。
257
+ // 抵触する場合、以下のように書き換える。
258
+ //
259
+ // const Component = React.memo<Props>((props) => {
260
+ // ...
261
+ // });
262
+ //
263
+ // export { Component as SomeComponent };
264
+ },
265
+ ],
266
+
267
+ regexp: (pattern: string) => [
268
+ {
269
+ // Validate that component names match the provided pattern
270
+ selector: `Identifier[name=${pattern}][parent.type='VariableDeclarator'][parent.init.type='CallExpression'][parent.init.callee.object.name='React'][parent.init.callee.property.name='memo']`,
271
+ message: `The component name should match ${pattern}.`,
272
+ },
273
+ ],
274
+ },
275
+
276
+ /* Restrict React hooks definition style */
277
+ reactHooksDefinitionStyle: [
278
+ {
279
+ // Ban "React.useImperativeHandle"
280
+ selector:
281
+ "MemberExpression[object.name='React'][property.name='useImperativeHandle']",
282
+ message:
283
+ 'Move logic to parent component instead of using React.useImperativeHandle.',
284
+ },
285
+ {
286
+ // Restrict type annotation style for React.useMemo
287
+ selector:
288
+ "TSTypeAnnotation[parent.parent.type='CallExpression'][parent.parent.callee.object.name='React'][parent.parent.callee.property.name='useMemo']",
289
+ message:
290
+ 'The variable type T should be annotated as `React.useMemo<T>` or `const v: T = React.useMemo(...)`.',
291
+ },
292
+ ],
293
+ } as const satisfies Record<
294
+ string,
295
+ | EslintRulesOption['no-restricted-syntax']
296
+ | Record<
297
+ string,
298
+ (...args: readonly never[]) => EslintRulesOption['no-restricted-syntax']
299
+ >
300
+ >;
301
+
87
302
  /** @link https://github.com/eslint/eslint/blob/main/conf/eslint-all.js */
88
303
  export const eslintRules = {
89
304
  /**
@@ -295,44 +510,7 @@ export const eslintRules = {
295
510
  *
296
511
  * AST checker: https://typescript-eslint.io/play/#ts=5.9.3&showAST=es&fileType=.ts&code=LAKCA&eslintrc=N4KABGBEBOCuA2BTAzpAXGYBfEWg&tsconfig=N4KABGBEDGD2C2AHAlgGwKYCcDyiAuysAdgM6QBcYoEEkJemy0eAcgK6qoDCAFutAGsylBm3TgwAXxCSgA&tokens=false
297
512
  */
298
- 'no-restricted-syntax': [
299
- 'error',
300
-
301
- {
302
- // ban "in" operator
303
- selector: "BinaryExpression[operator='in']",
304
- message: 'use "Object.hasOwn" instead.',
305
- },
306
- {
307
- // ban Object.prototype.hasOwnProperty.call
308
- selector:
309
- "MemberExpression[object.object.object.name='Object'][object.object.property.name='prototype'][object.property.name='hasOwnProperty'][property.name='call']",
310
- message: 'use "Object.hasOwn" instead.',
311
- },
312
- {
313
- // ban "new Array" expression
314
- selector: "NewExpression[callee.name='Array']",
315
- message: 'use Array.from instead.',
316
- },
317
- {
318
- // ban "React.useImperativeHandle"
319
- selector:
320
- "MemberExpression[object.name='React'][property.name='useImperativeHandle']",
321
- message: 'pass Observable via props instead.',
322
- },
323
- // replaced by @typescript-eslint/consistent-type-assertions and total-functions/no-unsafe-type-assertion
324
- // {
325
- // // ban "as"
326
- // selector: "TSAsExpression[typeAnnotation.typeName.name!='const']",
327
- // message: "Don't use `as`.",
328
- // },
329
- // {
330
- // selector:
331
- // "Identifier[name='draft'][parent.parent.callee.name!='produce'][parent.parent.parent.parent.parent.parent.callee.name!='produce']",
332
- // message:
333
- // "Don't use the identifier name `draft` except in immer produce function.",
334
- // },
335
- ],
513
+ 'no-restricted-syntax': ['error', ...restrictedSyntax],
336
514
 
337
515
  'no-return-assign': withDefaultOption('error'),
338
516
  'no-self-assign': withDefaultOption('error'),
@@ -135,7 +135,7 @@ export const eslintUnicornRules = {
135
135
  'unicorn/string-content': withDefaultOption('error'),
136
136
  'unicorn/switch-case-braces': 'off', // TODO: Enable this
137
137
  'unicorn/template-indent': withDefaultOption('error'),
138
- 'unicorn/text-encoding-identifier-case': 'error',
138
+ 'unicorn/text-encoding-identifier-case': withDefaultOption('error'),
139
139
  'unicorn/throw-new-error': 'error',
140
140
  'unicorn/no-unreadable-iife': 'error',
141
141
  'unicorn/no-useless-switch-case': 'error',
@@ -205,6 +205,10 @@ export const eslintUnicornRules = {
205
205
  // See #1396
206
206
  'unicorn/require-post-message-target-origin': 'off',
207
207
 
208
+ 'unicorn/no-immediate-mutation': 'error',
209
+ 'unicorn/no-useless-collection-argument': 'error',
210
+ 'unicorn/prefer-response-static-json': 'error',
211
+
208
212
  // For Node.js environment only
209
213
  'unicorn/no-new-buffer': 'error',
210
214
  'unicorn/no-process-exit': 'off',
@@ -914,6 +914,23 @@ namespace TestCaseShorthandStrings {
914
914
  | 'off';
915
915
  }
916
916
 
917
+ /**
918
+ * Enforce that all test cases with names have unique names
919
+ *
920
+ * @link https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/unique-test-case-names.md
921
+ *
922
+ * ```md
923
+ * | key | value |
924
+ * | :---------- | :--------- |
925
+ * | type | suggestion |
926
+ * | deprecated | false |
927
+ * | recommended | false |
928
+ * ```
929
+ */
930
+ namespace UniqueTestCaseNames {
931
+ export type RuleEntry = Linter.StringSeverity;
932
+ }
933
+
917
934
  export type EslintPluginRules = {
918
935
  readonly 'eslint-plugin/consistent-output': ConsistentOutput.RuleEntry;
919
936
  readonly 'eslint-plugin/fixer-return': FixerReturn.RuleEntry;
@@ -948,6 +965,7 @@ export type EslintPluginRules = {
948
965
  readonly 'eslint-plugin/require-test-case-name': RequireTestCaseName.RuleEntry;
949
966
  readonly 'eslint-plugin/test-case-property-ordering': TestCasePropertyOrdering.RuleEntry;
950
967
  readonly 'eslint-plugin/test-case-shorthand-strings': TestCaseShorthandStrings.RuleEntry;
968
+ readonly 'eslint-plugin/unique-test-case-names': UniqueTestCaseNames.RuleEntry;
951
969
  };
952
970
 
953
971
  export type EslintPluginRulesOption = {
@@ -248,7 +248,8 @@ namespace UseMemo {
248
248
  }
249
249
 
250
250
  /**
251
- * Validates that useMemos always return a value. See [`useMemo()`
251
+ * Validates that useMemos always return a value and that the result of the
252
+ * useMemo is used by the component/hook. See [`useMemo()`
252
253
  * docs](https://react.dev/reference/react/useMemo) for more information.
253
254
  *
254
255
  * ```md