TypeScript プロジェクトに ESLint・Prettier・cspell を 1 つずつ入れて挙動を確かめてみた

TypeScript プロジェクトに ESLint・Prettier・cspell を 1 つずつ入れて挙動を確かめてみた

2026.06.21

製造ビジネステクノロジー部の小林です。

普段何気なく Linter を使っていますが、「結局それぞれ何をしているのか」が曖昧なままだったので、ゼロから TypeScript プロジェクトを立ち上げ、ESLint・Prettier・cspell を 1 つずつ入れて挙動を確かめてみました。

Linter とは

Linter は、ソースを実行せずに読むだけで問題を見つけてくれるツールです。これを静的解析と呼びます。

https://typescript-eslint.io/

レビューで人間が気づくような「ここ any 使ってない?」「インデント揃ってないよ」「タイポしてるよ」という指摘を、機械が全ファイルチェックしてくれるイメージです。

Linter 系ツールの棲み分け

「Linter」とまとめて呼ばれがちですが、厳密にはそれぞれ役割が違います。

ツール種別 何をする?
Linter コードの「品質」「規約違反」を検出 ESLint
Formatter コードの「見た目」を自動整形 Prettier
Spell Checker スペルミスを検出 cspell
Type Checker 型の整合性を検証 TypeScript Compiler
Test Runner コードを実行して挙動を検証 Jest, Vitest

守備範囲を図にするとこんな感じです。

今回は、ESLint、Prettier、cspell の 3 つを順に入れていきます。

環境準備

前提

  • Node.js v22
  • パッケージマネージャは pnpm を使用(npm/yarn でも同様)
  • macOS で動作確認

プロジェクトを作る

まずは作業ディレクトリを用意します。

mkdir linter && cd linter
pnpm init

スクリーンショット 2026-06-20 16.23.01

TypeScript を入れる

題材は TypeScript なので、tsc を入れて tsconfig.json を初期化します。

pnpm add -D typescript @types/node
pnpm exec tsc --init

スクリーンショット 2026-06-20 16.23.56

package.jsonscripts に型チェック用のコマンドを足しておきます。

{
  "scripts": {
    "check:type": "tsc --noEmit"
  }
}

わざとダメなコードを置く

linter の挙動を確かめるための題材コードを src/sample.ts として用意します。

src/sample.ts
var counter = 0;
const recieveMsg = function (msg: any) {
  console.log(msg);
  return msg;
};

短いコードですが、ルール違反が仕込んであります。

ESLint を入れてみる

インストール

ESLint と TypeScript 用プラグイン、それと Flat Config を扱いやすくする @eslint/js を入れます。

pnpm add -D eslint @eslint/js typescript-eslint

スクリーンショット 2026-06-20 23.54.02

最小の eslint.config.js

ESLint v9 以降は Flat Config が標準です。配列の各要素が「ルールセット」で、files で対象を絞っていく仕組みです。

まずは最小限から始めます。

eslint.config.js
import js from "@eslint/js";
import tseslint from "typescript-eslint";

export default [
  js.configs.recommended,
  ...tseslint.configs.recommended,
  {
    files: ["**/*.ts"],
    rules: {
      "no-console": "error",
    },
  },
];

ここで rules に書いているのは no-console だけ です。これは意図的なもので、js.configs.recommended と tseslint.configs.recommended を読み込んだ時点で、以下のルールはすでに有効になっているからです。

recommended は、それぞれが推奨ルールの詰め合わせです。
js.configs.recommended … @eslint/js が提供する、ESLint 公式の推奨ルールセット。no-var など JavaScript 全般のルールが入っています。
https://eslint.org/docs/latest/rules/
tseslint.configs.recommended … typescript-eslint が提供する、TypeScript 向けの推奨ルールセット。@typescript-eslint/no-explicit-any など型を絡めたルールが入っています
https://typescript-eslint.io/users/configs/#recommended

ルール 含まれている recommended
no-var eslint:recommended
@typescript-eslint/no-unused-vars typescript-eslint:recommended
@typescript-eslint/no-explicit-any typescript-eslint:recommended

no-console だけはどちらの recommended にも入っていないので明示的に有効化しています。

files の効きどころ

Flat Config の files は、それが書かれているオブジェクトの中だけ で効きます。配列全体に掛かるわけではありません。

今回の設定をもう一度見てみます。

export default [
  { ignores: ["dist/**", "coverage/**"] },
  js.configs.recommended,           // files なし → 全ファイルに適用
  ...tseslint.configs.recommended,  // files なし → 全ファイルに適用
  {
    files: ["**/*.ts"],             // このブロック内だけ .ts に限定
    rules: {
      "no-console": "warn",
    },
  },
];

files が書かれているのは最後のブロックだけなので、適用範囲は次のようになります。

設定 適用範囲
js.configs.recommended 全ファイル
tseslint.configs.recommended 全ファイル
カスタム rules(no-console) .ts のみ

除外設定(ignores)

設定の先頭にある { ignores: [...] } が除外指定です。Flat Config では、ignores だけを持つオブジェクトを置くと、それが全体に効く除外設定になります。

node_modules はデフォルトで除外されるので、dist やカバレッジ出力など、自分のプロジェクト固有のものだけ書けば十分です。

scripts への登録

package.json の scripts に登録します。

{
  "scripts": {
    "check:lint": "eslint . --cache",
    "fix:lint": "pnpm check:lint --fix"
  }
}

--cache を付けると 2 回目以降の実行が速くなりますが、.eslintcache というファイルが生成されます。コミットする必要はないので .gitignore に追加しておきましょう。

動かしてみる

pnpm check:lint

実行すると、わざとダメに書いた sample.ts に対して以下のようなエラーが出ました。

スクリーンショット 2026-06-21 8.17.51

src/sample.ts
 error    Unexpected var, use let or const instead         no-var
 error    'counter' is assigned a value but never used     @typescript-eslint/no-unused-vars
 error    'recieveMsg' is assigned a value but never used  @typescript-eslint/no-unused-vars
 error    Unexpected any. Specify a different type         @typescript-eslint/no-explicit-any
 warning  Unexpected console statement                     no-console

recommended を読み込むだけでこれだけのチェックが効いています。また、自分で追加した 1 行(no-console)もきちんと機能していることが確認できます。

内部で何をしているのか

最初は「コードを文字列として、正規表現で見ているのかな?」と思っていました。var という文字を探しているだけ、のようなイメージです。でも実際は違いました。

ESLint は、ソースコードを一旦 AST(抽象構文木) という構造化されたデータに変換します。AST は、コードを「変数宣言」「関数」「呼び出し」といった意味のかたまりとして木構造で表現したものです。

ESLint はこの木を上からたどり、各ルールが「自分の探しているパターンに一致するノード」を見つけたら違反として報告します。

https://eslint.org/docs/latest/contribute/architecture/

ざっくり流れにするとこうです。

例えば no-var ルールは、AST の中から VariableDeclaration ノードで kind === "var" のものを探す、という仕組みです。「構文として理解した上で違反箇所を検出している」というのがポイントです。

自動修正してみる

--fix で直せるのは fixer が定義されているルールだけ です。実際に試してみます。

pnpm fix:lint

スクリーンショット 2026-06-21 8.29.36
スクリーンショット 2026-06-21 8.35.23

実行前に出ていた no-var のエラーが消えました。var → let への書き換えは機械的に決まるため、自動修正されました。一方で残った 3 種は、いずれも手で直す必要があります。

ルール 自動修正 理由
no-var 直った var → let / const への置換は機械的に決まる
no-unused-vars 残る 変数を消すか使うかは、書き手の意図次第
no-explicit-any 残る 何の型にすべきかは文脈次第で、機械的に決められない
no-console 残る 消す・残す・別のログ手段にするなど、判断が必要

Prettier を入れてみる

ESLint が品質を見るツールなら、Prettier は見た目を整えるツールです。

ESLint と何が違うのか

ESLint Prettier
目的 コード品質 コード整形
検出例 anyの使用、未使用変数 インデント、改行、クォート
思想 設定可能 基本ノンカスタマイズ

インストール

pnpm add --save-dev --save-exact prettier

スクリーンショット 2026-06-21 8.46.31

.prettierrc.json を書く

ルートに .prettierrc.json を置きます。設定項目は少なめで OK です。

{
  "trailingComma": "all",
  "tabWidth": 2,
  "semi": true,
  "singleQuote": false,
  "printWidth": 80
}
設定 効果
trailingComma: "all" 末尾カンマを必ずつける。diff が綺麗になる
tabWidth: 2 インデント 2 スペース
semi: true 文末セミコロン必須
singleQuote: false ダブルクォート "..." 統一
printWidth: 80 80 文字超えたら改行

scripts に登録

{
  "scripts": {
    "check:format": "prettier --check --cache \"./**/*.{ts,tsx}\"",
    "fix:format": "prettier --write --cache \"./**/*.{ts,tsx}\""
  }
}

動かしてみる

pnpm check:format

スクリーンショット 2026-06-21 8.53.36

整形前後の差分を見たいので、わざとフォーマットを崩したコードを用意してみます。

// 整形前
// これは80文字を超えるので縦に展開される
const user = { name: "taro", age: 30, hobbies: ["music", "sports", "reading", "cooking"],};

pnpm fix:format を流すと…

スクリーンショット 2026-06-21 12.48.25

// Prettier整形後
const user = {
  name: "taro",
  age: 30,
  hobbies: ["music", "sports", "reading", "cooking"],
};

printWidth: 80 を超えるオブジェクトリテラルは縦に展開される」というルールで、毎回統一的に整形してくれます。

ESLint との競合をどう解決するか

ESLint にもフォーマット系のルール(インデントとか改行とか)があるので、何もしないと Prettier と ESLint が同じ箇所で違うことを言い出します。

これを解決するために eslint-config-prettier を入れます。

pnpm add -D eslint-config-prettier

スクリーンショット 2026-06-21 12.50.43

eslint.config.js に追記します。

import js from "@eslint/js";
import tseslint from "typescript-eslint";
import eslintConfigPrettier from "eslint-config-prettier";

export default [
  { ignores: ["dist/**", "coverage/**"] },
  js.configs.recommended,
  ...tseslint.configs.recommended,
  {
    files: ["**/*.ts"],
    rules: {
      "no-console": "warn",
      "prefer-arrow-callback": "error",
      "func-style": ["error", "expression", { allowArrowFunctions: true }],
    },
    eslintConfigPrettier, // ← 必ず後ろに置く
  },
];

これは 「ESLint のフォーマット系ルールを全部 OFF にする」設定 です。

役割分担を図にするとこうなります。

フォーマットは Prettier に完全委譲、ESLint は品質に専念という棲み分けです。

cspell を入れてみる

最後はスペルチェッカーです。

なぜスペルチェックが必要か

// よくあるタイポ
const recieveMessage = () => {}; // 正: receive
const lenght = arr.length; // 正: length

こういうコードを未然に防いでくれます。コードだけではなくてマークダウンファイルのタイポも防いでくれます。

インストール

pnpm add -D cspell

cspell.json を書く

ルートに cspell.json を置きます。最初は空っぽで OK です。

{
  "words": [],
  "ignorePaths": []
}

scripts に登録します。

{
  "scripts": {
    "check:spell": "cspell \"**/*\" --cache --gitignore"
  }
}

動かしてみる

題材コードには、わざとスペルミスを仕込んだ recieveMsg(正しくは receiveMsg)があります。この状態で CSpell を実行します。

pnpm check:spell

スクリーンショット 2026-06-21 13.11.57
スクリーンショット 2026-06-21 13.16.32

recieveMsg がスペルミスとして検出されます。

src/sample.ts:2:7 - Unknown word (recieve) fix: (receive)

ただ、このとき pnpm-lock.yaml まで検査対象に含まれてしまい、大量の指摘が出ることがあります。

lock ファイルにはパッケージ名やハッシュ値のような文字列が大量に並んでいるため、それらが軒並み「知らない単語」として検出されてしまうのです。lock ファイルは自動生成されるもので人間が綴りを気にする対象ではないので、次に説明する ignorePaths で検査対象から外します。

プロジェクト固有の単語を登録する

ライブラリ名や独自ドメイン用語は辞書にありません。cspell.json の words に追加して、辞書を育てていきます。あわせて、先ほどの pnpm-lock.yaml のように 検査しても意味がないファイルは ignorePaths で除外 します。

{
  "words": [""],
  "ignorePaths": ["pnpm-lock.yaml", "node_modules/**", "dist/**"]
}

この状態で動かしてみます。

スクリーンショット 2026-06-21 13.46.48

pnpm-lock.yaml のエラーが消えました。続いて recieveMsg のスペルミスを直して実行します。
スクリーンショット 2026-06-21 15.10.02

エラーが全て消えました!

モノレポでは辞書を分割できる

モノレポ構成では、ルートだけでなく各パッケージにも cspell.json を置けます。そして CSpell は、チェック対象ファイルに近い場所の設定から順に適用してくれます。

./cspell.json プロジェクト全体
./packages/backend/cspell.json backend独自
./packages/infrastructure/.../cspell.json 構成要素独自

これの何が嬉しいかというと、「そのパッケージでしか使わない用語」を、そのパッケージの中だけで許容できる点です。

ルートに「共通の用語」、各パッケージに「そこ固有の用語」と置き場所を分けることで、ルートの cspell.json の辞書の肥大化を防げます。

3 つを統合する

ここまでで ESLint・Prettier・cspell を入れたので、最終的な package.jsonscripts は次のようになります。

{
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "check:type": "tsc --noEmit",
    "check:lint": "eslint . --cache",
    "fix:lint": "pnpm check:lint --fix",
    "check:format": "prettier --check --cache \"./**/*.{ts,tsx}\"",
    "fix:format": "prettier --write --cache \"./**/*.{ts,tsx}\"",
    "check:spell": "cspell \"**/*\" --cache --gitignore"
  }
}

命名ルール

スクリプト名に命名規則を設けておくと運用がスムーズです。

  • check:* → チェックのみ行い、ファイルは変更しない
  • fix:* → チェックに加えて自動修正まで実行する

「壊さない(チェックだけの)コマンド」と「直してくれる(自動修正する)コマンド」を明確に分けるのがポイントです。

CI/CD への組み込み

プルリクエスト作成時に CI で以下を自動実行するようにすると、レビュー前の機械チェックが一通り走ります。

pnpm check:format
pnpm check:lint
pnpm check:type
pnpm check:spell

まとめ

今回は、ESLint・Prettier・cspell を実際にゼロから入れて触ってみました。最初に厳しいルールを入れるのではなく、開発を進めながら育てていく感じがよいのかなと思いました。

この記事をシェアする

関連記事