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.
- package/README.md +62 -15
- package/dist/configs/jest.d.mts.map +1 -1
- package/dist/configs/preact.d.mts.map +1 -1
- package/dist/configs/preact.mjs.map +1 -1
- package/dist/configs/react.mjs.map +1 -1
- package/dist/configs/testing-library.d.mts.map +1 -1
- package/dist/configs/typescript-without-rules.d.mts.map +1 -1
- package/dist/configs/typescript-without-rules.mjs +10 -1
- package/dist/configs/typescript-without-rules.mjs.map +1 -1
- package/dist/configs/typescript.d.mts.map +1 -1
- package/dist/configs/typescript.mjs.map +1 -1
- package/dist/configs/vitest.d.mts.map +1 -1
- package/dist/entry-point.d.mts +1 -1
- package/dist/entry-point.d.mts.map +1 -1
- package/dist/entry-point.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/rules/eslint-import-rules.d.mts.map +1 -1
- package/dist/rules/eslint-import-rules.mjs +1 -0
- package/dist/rules/eslint-import-rules.mjs.map +1 -1
- package/dist/rules/eslint-plugin-rules.d.mts +1 -0
- package/dist/rules/eslint-plugin-rules.d.mts.map +1 -1
- package/dist/rules/eslint-plugin-rules.mjs +1 -0
- package/dist/rules/eslint-plugin-rules.mjs.map +1 -1
- package/dist/rules/eslint-react-rules.mjs +1 -1
- package/dist/rules/eslint-rules.d.mts +67 -3
- package/dist/rules/eslint-rules.d.mts.map +1 -1
- package/dist/rules/eslint-rules.mjs +182 -36
- package/dist/rules/eslint-rules.mjs.map +1 -1
- package/dist/rules/eslint-unicorn-rules.d.mts +4 -1
- package/dist/rules/eslint-unicorn-rules.d.mts.map +1 -1
- package/dist/rules/eslint-unicorn-rules.mjs +4 -1
- package/dist/rules/eslint-unicorn-rules.mjs.map +1 -1
- package/dist/rules/index.mjs +1 -1
- package/dist/types/rules/eslint-plugin-rules.d.mts +17 -0
- package/dist/types/rules/eslint-plugin-rules.d.mts.map +1 -1
- package/dist/types/rules/eslint-react-hooks-rules.d.mts +2 -1
- package/dist/types/rules/eslint-react-hooks-rules.d.mts.map +1 -1
- package/dist/types/rules/eslint-unicorn-rules.d.mts +219 -142
- package/dist/types/rules/eslint-unicorn-rules.d.mts.map +1 -1
- package/package.json +11 -8
- package/src/configs/jest.mts +1 -1
- package/src/configs/preact.mts +36 -35
- package/src/configs/react.mts +1 -1
- package/src/configs/testing-library.mts +1 -1
- package/src/configs/typescript-without-rules.mts +10 -1
- package/src/configs/typescript.mts +74 -72
- package/src/configs/vitest.mts +1 -1
- package/src/entry-point.mts +2 -0
- package/src/rules/eslint-import-rules.mts +2 -0
- package/src/rules/eslint-plugin-rules.mts +1 -0
- package/src/rules/eslint-react-rules.mts +1 -1
- package/src/rules/eslint-rules.mts +216 -38
- package/src/rules/eslint-unicorn-rules.mts +5 -1
- package/src/types/rules/eslint-plugin-rules.mts +18 -0
- package/src/types/rules/eslint-react-hooks-rules.mts +2 -1
- 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
|
|
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
|