個人的にいいなと思った Eslint, Prettier, VSCode設定
はじめに
今回のコードはこちら↓
できる限りサンプルコードを記載するようにはしているが、最後らへんはめんどくさくなって省いているところも多いです。お許しください。
必要なパッケージ
npm install --save-dev eslint eslint-config-prettier eslint-plugin-unicorn eslint-plugin-functional prettier-plugin-organize-imports eslint-plugin-import eslint-plugin-react-hooks eslint-plugin-react eslint-plugin-prefer-arrow-functions eslint-import-resolver-typescript
ESLint
env
各環境下で定義されている global 変数を ESlint 実行環境で使用可能にするための設定。
browser: true,
es2020: true,
node: true,
parserOptions
sourceType: 'module'
モジュールとしてコードを解析する設定。
project: ['tsconfig.json']
@typescript-eslint/parser に必要な TypeScript の方情報にアクセスするための設定。
settings
react: { version: 'detect' }
プロジェクトで使用している React のバージョンに適した規則が適用される。
reportUnusedDisableDirectives
eslint-disable の不要になったコメントを削除する。
rules
非 null アサーション演算子!
の利用を禁止する。
❌ const test = hoge.fuga!.fizz
// Forbidden non-null assertion.eslint@typescript-eslint/no-non-null-assertion
命名規則を強制する。
Boolean型
が期待される式においてBoolean型以外
の型使用を禁止する。
個人的には厳しめの設定↓が好み
"@typescript-eslint/strict-boolean-expressions": [
"error",
{
"allowString": false,
"allowNumber": false,
"allowNullableObject": false,
"allowNullableBoolean": false,
"allowNullableString": false,
"allowNullableNumber": false,
"allowAny": false,
}
],
const hoge: string = "hoge"
❌ if (test) console.log('hoge');
// Unexpected nullable string value in conditional. Please handle the nullish/empty cases explicitly.eslint@typescript-eslint/strict-boolean-expressions
✅ if (test != null) console.log('hoge');
JSX で&&
の左辺をBoolean型
に強制したり、三項演算子に強制したりする。
validStrategies
にはcoerce
とternary
2つのオプションがあり、coerce
は前者、ternary
は後者の強制をになっている。オプションは1つ以上設定する必要あり。
@typescript-eslint/strict-boolean-expressions
と組み合わせて使用する場合は、ternary
のみの指定となる。
const test1: string = ""
const test2: string = "test2"
return (
<>
❌ {test1 && test2}
{/* Potential leaked value that might cause unintentionally rendered values or rendering crasheseslintreact/jsx-no-leaked-render */}
✅ {test1 != null ? test2 : null}
</>
)
+foo
や!foo
の型変換の短い表記法を禁止する。
const num = "1"
❌ const num2 = +num
use `Number(num)` instead.eslintno-implicit-coercion
✅ const num2 = Number(num)
文字列の結合に+
を使用することを禁止する。
const test2: string = "test2"
return (
<>
❌ {'hoge' + test2}
{/* Unexpected string concatenation.eslintprefer-template */}
✅ {`hoge${test2}`}
</>
)
※eslint . --fix
を実行すると{'hoge${ test2}'}
となるので気持ち悪い人は修正する必要がある。
異なる型の加算を禁止する。
個人的には厳しめの設定↓が好み
"@typescript-eslint/restrict-plus-operands": [
"error",
{
"allowAny": true,
"allowBoolean": true,
"allowNullish": true,
"allowNumberAndString": true,
"allowRegExp": true,
"skipCompoundAssignments": false,
}
],
const str: string = "1"
const num: number = 1
❌ const sum = num + str
// Operands of '+' operations must be a number or string. Got `number` + `string`.eslint@typescript-eslint/restrict-plus-operands
switch 文の網羅性を強制する。
"@typescript-eslint/switch-exhaustiveness-check": [
"error",
{
allowDefaultCaseForExhaustiveSwitch: true,
requireDefaultForNonUnion: true,
},
],
allowDefaultCaseForExhaustiveSwitch: true
全てのケースが網羅されている switch 文で default ケースを許容する。
requireDefaultForNonUnion: true
ユニオン型でない値の switch 文で default ケースを強制する。
type DayName = 'Monday' | 'Tuesday';
type Day = {
[key: string]: DayName
}
const day: Day = {
mon: 'Monday',
tue: 'Tuesday',
};
❌ switch(day.key) {
case "Monday":
console.log('hoge');
break;
}
// Switch is not exhaustive. Cases not matched: "Wednesday" | "Thursday" | "Friday" | "Saturday" | "Sunday"eslint@typescript-eslint/switch-exhaustiveness-check
✅ switch(day.key) {
case "Monday":
console.log('hoge');
break;
case "Tuesday":
console.log('hoge1');
break;
}
✅ switch(day.key) {
case "Monday":
console.log('hoge');
break;
default:
break;
}
✅ switch(day.key) {
case "Monday":
console.log('hoge');
break;
case "Tuesday":
console.log('hoge1');
break;
default:
break;
}
❌ const handler = (num: number) => {
switch(num) {
case 1:
console.log('hoge');
break;
}
}
}
// Switch is not exhaustive. Cases not matched: defaulteslint@typescript-eslint/switch-exhaustiveness-check
✅ const handler = (num: number) => {
switch(num) {
case 1:
console.log('hoge');
break;
default:
break;
}
}
if 文の網羅性を禁止し、 switch 文に強制する。
"unicorn/prefer-switch": [
"error",
{
"minimumCases": 2,
"emptyDefaultCase": "do-nothing-comment",
}
],
"minimumCases": 2
if {} else {}
の形のみ許容する。
"emptyDefaultCase": "do-nothing-comment"
else
がないif文
から整形する際にdefault
を出力する
switch文のfallthrough(breakやreturnを行わないこと)
を禁止する。
tsconfig にもnoFallthroughCasesInSwitch
という同じような設定が存在し、今回はReact(TypeScript)のプロジェクトに入れることを前提として考えているので、noFallthroughCasesInSwitch
を使用する。
既存の定義されているグローバル変数の使用を制限する。
一旦今回はこんな感じ↓
"no-restricted-globals": [
"error",
{
"name": "event",
"message": "Use local parameter instead. Global 'event' object can lead to unintended behavior."
},
{
"name": "isNaN",
"message": "Use Number.isNaN instead to avoid type coercion."
},
{
"name": "isFinite",
"message": "Use Number.isFinite instead to avoid type coercion."
},
],
必要になれば都度追加していく。
❌ console.log(isNaN(num));
// Unexpected use of 'isNaN'. Use Number.isNaN instead to avoid type coercion.eslintno-restricted-globals
❌ console.log(isFinite(num));
Unexpected use of 'isFinite'. Use Number.isFinite instead to avoid type coercion.eslintno-restricted-globals
❌ const onClick = () => {
console.log(event);
}
// Unexpected use of 'event'. Use local parameter instead. Global 'event' object can lead to unintended behavior.eslintno-restricted-globals
✅ console.log(Number.isNaN(num));
✅ console.log(Number.isFinite(num));
✅ const onClick = (event) => {
console.log(event);
}
-
functional/no-let
let
の使用を禁止する。
"functional/no-let": [
"error",
{
"allowInForLoopInit": true,
}
]
"allowInForLoopInit": true
for (let i = 0; i < array.length; i++) {}
みたいに、for 文の引数では許容する。
❌ let hoge = 1;
const t = () => {
❌ let hoge2 = 1
}
// Unexpected let, use const instead.eslintfunctional/no-let
✅ const hoge3 = 1;
✅ for( let i = 0; i < 5; i++) {}
-
functional/immutable-data
既存の配列とオブジェクトの変更を禁止する。
'functional/immutable-data': [
'error',
{
ignoreClasses: true,
ignoreImmediateMutation: true,
ignoreAccessorPattern: [
'**.current.**',
'**.scrollTop',
],
},
],
ignoreClasses: true,
Classes 内の変更は許容する。
ignoreImmediateMutation: true,
オブジェクトを変数に代入する前の変更は許容する。
ignoreAccessorPattern: ['**.current.**', '**.scrollTop',],
ref, scrollTop の変更は許容する。
const obj = { foo: 1 }
❌ obj.bar = 2;
// Modifying an existing object/array is not allowed.eslintfunctional/immutable-data
-
@typescript-eslint/method-signature-style
メソッドシグネチャを禁止する。 -
import/no-cycle
循環 import を禁止する。 -
prettier-plugin-organize-imports
TypeScript の organizeImport API を使って、不要な import を削除などする。 -
import/newline-after-import
import ブロックの後に1行改行を強制する。 -
import/no-useless-path-segments
import で不要なパスを禁止する。 -
@typescript-eslint/consistent-type-imports
import する型に type をつけることを強制する。
今回は下記↓を適用。
"@typescript-eslint/consistent-type-imports": [
"error",
{
"prefer": 'type-imports',
"disallowTypeAnnotations": false,
"fixStyle": 'inline-type-imports',
}
],
"prefer": 'type-imports'
type の import では {type 〇〇} を強制する。
"disallowTypeAnnotations": false
型注釈での import を禁止する。
"fixStyle": 'inline-type-imports'
type を inline にする。
-
@typescript-eslint/consistent-type-exports
type の export で "type" の記載を強制する。
今回の下記↓の設定。
"@typescript-eslint/consistent-type-exports": [
"error",
{
"fixMixedExportsWithInlineTypeSpecifier": true,
}
],
"fixMixedExportsWithInlineTypeSpecifier": true
関数や変数と型をまとめて export する際に、 inline で書くことを許容する。
-
@typescript-eslint/require-array-sort-compare
数値に引数なしのsort()
を使うとバグが発生しうるため、引数を強制する。
今回は下記↓の設定。
"@typescript-eslint/require-array-sort-compare": [
"error",
{
"ignoreStringArrays": true,
}
],
"ignoreStringArrays": true
文字列の配列では許容する。
-
react/jsx-no-bind
無駄なレンダリングが起こり得るコードを禁止する。
今回は下記↓の設定。
"react/jsx-no-bind": [
"error",
{
"allowArrowFunctions": true,
}
],
"allowArrowFunctions": true
何でもかんでも useCallback 使えばいいというのは嫌いなので、アロー関数のみ有効にする。
-
eslint-plugin-react-hooks
plugin:react-hooks/recommended
でも warn 設定がされているが、 error に強制する。
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "error",
"react-hooks/rules-of-hooks": "error"
Hooks の正しいルールを強制する。
"react-hooks/exhaustive-deps"
deps を強制する。
-
func-style
アロー関数へ強制する。 -
prefer-arrow-functions/prefer-arrow-functions
アロー関数へ自動修正する。 -
arrow-body-style
中括弧を省略できる場合は、許容する。 -
react/react-in-jsx-scope
React v17以降でimport React from 'react'
の省略を許容する。 -
import/order
import を自動ソートする。
今回は下記↓の設定。
"import/order": [
"error",
{
groups: [
"builtin",
"external",
"internal",
"parent",
"sibling",
"index",
"object",
"type",
],
pathGroups: [
{
pattern: "{react,react-dom/**,react-router-dom}",
group: "external",
position: "before",
},
{
pattern: "~/*",
group: "internal",
position: "before",
},
],
pathGroupsExcludedImportTypes: ["builtin"],
alphabetize: {
order: "asc",
caseInsensitive: true,
},
"newlines-between": "always",
},
],
groups: ["builtin", "external", "internal", "parent", "sibling", "index", "object", "type"],
import をカテゴリ分けする。
builtin:Node.jsの組み込みモジュール(例: fs, pathなど)
external:外部からインストールされたモジュール(例: react, lodashなどのnpmパッケージ)
internal:プロジェクト内で定義されたエイリアス(例: ~/foo/bar)
parent:親ディレクトリ(例: ../)
sibling:同一ディレクトリ内のファイル(例: ./foo)
index:同一ディレクトリ内のindexファイル(例: ./)
object:動的インポート(例: import log = console.log;)
type:TypeScriptの型定義(例: import type { Foo } from 'foo';)
pathGroups: [{ pattern: "{react,react-dom/**,react-router-dom}", group: "external", position: "before" }, { pattern: "react-dom/**", group: "external", position: "before" }, { pattern: "react-router-dom", group: "external", position: "before" }]
path ごとにグループ化する。
pattern:指定したいパスの文字列。 minimatch パターンでかける。
group:pattern で指定した対象の属するグループを指定する。
position:import する位置を group で指定した対象の相対位置で指定する。
pathGroupsExcludedImportTypes: ["builtin"]
pathGroups の設定から除外する group を指定する。
alphabetize: { order: "asc" }
昇順に並び替える。
alphabetize: { caseInsensitive: true }
大文字小文字をの区別を無視する。
"newlines-between": "always"
group 間の改行を強制する。
参考記事
Prettier
みんなにわかりやすいコードを目指す上で Prettier はデフォルトの設定を用いるのが一番いいと思われるので、特にカスタマイズはしない。
デフォルトの設定は下記↓
/** @type {import("prettier").Config} */
const config = {
printWidth: 80, // 折り返し位置
tabWidth: 2, // インデントサイズ
useTabs: false, // タブでインデント
semi: true, // 文末のセミコロン
singleQuote: false, // シングルクウォートの使用
quoteProps: "as-needed", // オブジェクトプロパティのクォート
jsxSingleQuote: false, // JSX でのシングルクウォートの使用
trailingComma: "all", // 末尾のカンマ
bracketSpacing: true, // オブジェクト内の要素と括弧の間のスペース
bracketSameLine: false, // HTML タグの閉じ括弧を同一行に
arrowParens: "always", // アロー関数の引数括弧
rangeStart: 0, // フォーマット適用開始位置
rangeEnd: Infinity, // フォーマット適用完了位置
requirePragma: false, // プラグマを含むファイルのみにフォーマット適用
insertPragma: false, // プラグマの挿入
proseWrap: "preserve", // マークダウンテキストのテキスト折り返し
htmlWhitespaceSensitivity: "css", // 空白感度の指定
endOfLine: "lf", // 改行コードの指定
embeddedLanguageFormatting: "auto", // 組み込み言語のフォーマット
singleAttributePerLine: false, // HTML, JSX で1行1属性
};
module.exports = config;
VSCode
VSCode もさまざまなカスタマイズがあるが、 ESLint と Prettier を活かす最低限の設定にする。
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "always" // ファイル保存時にESLintによる自動修正を実行する。
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
], // ESLintがチェックを行うファイルタイプを指定する。
"typescript.tsdk": "node_modules/typescript/lib", // プロジェクトで使用されているTypeScriptのバージョンをVS Codeに指定する。
"editor.formatOnSave": true, // ファイル保存時にフォーマットを自動で実行する。
"prettier.enable": true // Prettierを有効にする。
}
(おまけ)エイリアスの設定
tsconfig.json と vite.config.ts それぞれに下記設定を追記する。
"compilerOptions": {
・・・
"baseUrl": "./",
"paths": {
"~/*": ["./src/*"]
}
},
export default defineConfig({
・・・
resolve: {
alias: {
"~/": path.resolve(__dirname, "./src"),
},
},
});
以上!
Discussion