2024.01.24
ESLintのruleについて1時間ひたすら話すミーティングを開いてみた
メンヘラテクノロジーではレビュアーがボトルネックになってしまい、開発スピードを上げづらいという課題があります。
そのため、レビューコストをできるだけ下げ、レビュアーがボトルネックにならないようにする取り組みを定期的に行っています。
今回はその中で行った、「ESLintのruleについてだけ1時間ひたすら話すミーティング」について書きました。
ミーティングの結果、出来上がった .eslintrc
は最後に載せています。
課題感
メンヘラテクノロジーは開発しているプロダクトが増え、エンジニアチームの人数も増えましたが、コードレビューを担当するメンバーはあまり増えていません。
その結果、プルリクエストの数に対してレビュアー(私)のキャパが不足し、レビュー待ちのタスクが積み上がってしまうことがあります。
そのような状況の中で、ちょっとした指摘で修正と再レビューが発生するのはもったいないと感じていました。
たとえば、console.log
の消し忘れや、大文字小文字の指摘で一往復するのはよくある話ではないでしょうか。
本来行いたい設計面や実装意図のレビューに集中できるように、ルールベースで対応できる内容はできるだけlinterに任せたいと考えています。
ゴール
レビューコストを下げられるように、欲しいESLintのruleはすべて設定したいです。 ミーティングが終わったときには必要なruleが洗い出されており、あとは実装者がeslintrcを書いていくだけでよい状態まで持っていくことをゴールにしました。
事前準備
どんなルールを設定するべきかを決めるため、まずは直近のレビューでルールベースで対応できそうな指摘を洗い出します。 3ヶ月分のプルリクエストを遡り、「このコメントは他のPRでも書いたな」「これってレビューの段階で指摘する必要あるんだっけ」と思ったコメントを中心にピックアップしました。
次に、洗い出したコメントに対応するESLintのルールを事前に調べておきます。ESLintでは対応できないものもあるため、とりあえずルールがありそうかどうかだけ探しておきます。
ミーティングの進め方
洗い出したコメントと探したルールを見ながら、ルールをどう設定すべきかを一つずつディスカッションしていきます。たとえば変数の命名規則のルールではbooleanの変数名のprefixはどうあるべきかなどについて決めました。 ルールを見つけられなかったものや、既存のルールでは対応できないものはlinterでは対応せず、コーディングのガイドラインに記載することにしました。
ミーティング後
設定することを決めたルールを .eslintrc
に書いていきます。
linterでは対応しないことに決めた内容をガイドラインに記載する作業も合わせてするとよいでしょう。
さいごに
さいごに今回の成果物を載せておきます。 1時間のミーティングの結果、出来上がったeslintrcはこのようになりました。
// .eslintrc.cjs
module.exports = {
..., // 省略
parserOptions: {
rules: {
'prefer-const': 'error',
'prefer-arrow-callback': 'error',
'@typescript-eslint/consistent-type-imports': [
'warn',
{
prefer: 'type-imports',
},
],
'@typescript-eslint/no-unused-vars': [
'error',
{
varsIgnorePattern: '^_',
argsIgnorePattern: '^_',
},
],
// 詳細: https://typescript-eslint.io/rules/consistent-type-definitions/
'@typescript-eslint/consistent-type-definitions': ['error', 'type'],
// 詳細: @typescript-eslint/naming-convention
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'variable',
format: ['strictCamelCase', 'StrictPascalCase', 'UPPER_CASE'],
leadingUnderscore: 'allow',
},
{
selector: 'parameter',
format: ['strictCamelCase'],
},
{
selector: 'class',
format: ['StrictPascalCase'],
custom: {
regex: 'send|start|find',
match: false,
},
},
{
selector: 'typeLike',
format: ['StrictPascalCase'],
},
{
selector: 'enumMember',
format: ['StrictPascalCase'],
},
// 変数名のprefixの規則
{
selector: 'variable',
types: ['boolean'],
// prefix以降がPascalCaseである必要がある。検証の解決順はprefix -> format
format: ['PascalCase'],
prefix: ['is', 'can', 'should', 'has', 'did', 'will'],
},
],
// 詳細: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/array-type.md
'@typescript-eslint/array-type': [
'error',
{
default: 'generic',
},
],
'import/order': [
'error',
{
'newlines-between': 'always',
warnOnUnassignedImports: true,
},
],
'no-restricted-imports': [
'error',
{
patterns: ['../'],
},
],
// 詳細: https://eslint.org/docs/latest/rules/curly
curly: ['error', 'all'],
// 詳細: https://eslint.org/docs/latest/rules/object-shorthand
'object-shorthand': ['error', 'always'],
// 詳細: https://eslint.org/docs/latest/rules/no-nested-ternary
'no-nested-ternary': 'error',
// 詳細: https://eslint.org/docs/latest/rules/no-console
'no-console': ['error', { allow: ['warn', 'error'] }],
'react/react-in-jsx-scope': 'off',
'react/jsx-filename-extension': ['error', { extensions: ['.jsx', '.tsx'] }],
'react/prop-types': 'off',
'react/jsx-pascal-case': 'error',
// 詳細: https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md
'react/self-closing-comp': 'error',
// 詳細: https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-curly-brace-presence.md
'react/jsx-curly-brace-presence': 'error',
},
}