AWS CDK TypeScriptの命名規則ベストプラクティスをESLintでチェックする設定を入れよう

![画像1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/1a5aaa28-95c1-4444-a662-4f63af9a570e.png)

# 概要
AWS CDK TypeScriptで開発を行う際のベストプラクティスがAWS公式ドキュメントにて公開されています。
そのうちの一つにコード内の命名規則について以下のような記載があります。

> 命名規則を適用することでコードベースの整合性が保たれ、変数の命名方法を考える際のオーバーヘッドが軽減されます。次の構成を推奨します。
>* 変数名と関数名には camelCase を使用します
>* クラス名とインターフェイス名には PascalCase を使用します
>* インターフェイスメンバーには camelCase を使用します
>* 型名と列挙型名には PascalCase を使用します
>* camelCase を使用してファイルに名前 (例えば、ebsVolumes.tsx または storage.tsb) を付けます

https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/best-practices-cdk-typescript-iac/typescript-best-practices.html#naming-conventions

確実に上記内容を満たす必要があるわけではないですが、ベストプラクティスであり、コード可読性向上のためにも意識すべき内容かと思います。
ただ、これを全て暗記してやろうと思っても個人的には難しいなと思います。
そのため今回は、ESLint(JavaScriptやTypeScriptなどの静的解析ツール)の機能を使ってこれらのベストプラクティスを満たしているかチェックする設定について記事にしていきます。

# 前提条件
* VSCodeがインストールされていること
* VSCode拡張機能にESLint/Prettierがインストールされていること
* npm（Node.jsのパッケージ管理ツール）がインストールされていること <br>※Node.jsをインストールすることで一緒にインストールされます
* TypeScriptがインストールされていること
* AWS CDKがインストールされていること

# 環境設定
環境を設定していきましょう。
わかりやすいように作業フォルダをCDKのプロジェクトフォルダにした状態で準備していきます。
各モジュールのバージョンは以下の通りです。

|モジュール|バージョン|
|:-|:-|
|TypeScript|5.7.2|
|Node.js|22.12.0|
|npm|10.9.0|
|aws cdk|2.174.0|

### ESLint&Prettierをローカルインストール
VSCodeの拡張機能としてESLintが入っていたとしても実は使えません。また、Prettierを併用する場合はPrettierもインストールする必要があります。そのため各パッケージをインストールしていきます。
Node.jsのパッケージマネージャー(`npm`や`yarn`)を使ったNode.jsのパッケージインストール方式には「グローバルインストール」と「ローカルインストール」がありますが今回は「ローカルインストール」をしていきます。

|インストール方式|概要|インストールコマンド|
|:-|:-|:-|
|グローバル<br>インストール|PC上のどの場所からでも<br>利用する場合|npm install -g <パッケージ名>|
|ローカル<br>インストール|プロジェクト単位(フォルダ単位)<br>で利用する場合|npm install -D <パッケージ名>|

:::note info
* インストール方式は使用するパッケージによって使い分けるものですが、<br>基本的には**ローカルインストール**が推奨されます
* プロジェクトごとにバージョンを柔軟に管理することができるためです
:::

CDKのプロジェクトフォルダ内で以下のコマンドを実行しローカルインストールします。
```shell-session
$ npm install -D eslint prettier

added 60 packages, and audited 397 packages in 10s

52 packages are looking for funding
  run `npm fund` for details

1 low severity vulnerability

To address all issues, run:
  npm audit fix --force

Run `npm audit` for details.
```

インストールが完了すると、`package.json`の`devDependencies`に追加されます。
```diff_json:package.json
{
  "name": "work",
  "version": "0.1.0",
  "bin": {
    "work": "bin/work.js"
  },
  "scripts": {
    "build": "tsc",
    "watch": "tsc -w",
    "test": "jest",
    "cdk": "cdk"
  },
  "devDependencies": {
    "@types/jest": "^29.5.14",
    "@types/node": "22.7.9",
    "aws-cdk": "2.174.0",
+    "eslint": "^9.22.0",
    "jest": "^29.7.0",
+    "prettier": "^3.5.3",
    "ts-jest": "^29.2.5",
    "ts-node": "^10.9.2",
    "typescript": "~5.6.3"
  },
  "dependencies": {
    "aws-cdk-lib": "2.174.0",
    "constructs": "^10.0.0"
  }
}
```

### その他パッケージのローカルインストール
その他必要なパッケージをローカルインストールしていきます。
インストールが完了すると、同様に`package.json`の`devDependencies`に追加されます。
```shell-session
$ npm install -D @eslint/js @eslint/eslintrc @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-unicorn eslint-config-prettier eslint-plugin-prettier

added 64 packages, and audited 461 packages in 18s

81 packages are looking for funding
  run `npm fund` for details

1 low severity vulnerability

To address all issues, run:
  npm audit fix --force

Run `npm audit` for details.
```

### ESLint設定ファイル
ESLint設定ファイルは以下の通りです。
```javascript:eslint.config.mjs
/* eslint-disable */
import { defineConfig } from "eslint/config"; // eslintの設定ファイルを定義するための関数をインポート
import typescriptEslint from "@typescript-eslint/eslint-plugin"; // TypeScriptに関連するESLintルールを提供するプラグイン
import unicorn from "eslint-plugin-unicorn"; // 一貫性のあるコードスタイルのためのユニコーンプラグイン
import prettier from "eslint-plugin-prettier"; // PrettierによるコードフォーマットをESLintルールとして適用するプラグイン
import path from "node:path"; // ファイルパス操作のためのNode.jsのpathモジュール
import { fileURLToPath } from "node:url"; // ファイルURLをパスに変換するNode.jsの関数
import js from "@eslint/js"; // JavaScriptに関するESLintの設定を提供するパッケージ
import { FlatCompat } from "@eslint/eslintrc"; // 複数の設定ファイルを組み合わせるための互換性モジュール

const __filename = fileURLToPath(import.meta.url); // 現在のファイル名を取得
const __dirname = path.dirname(__filename); // 現在のディレクトリ名を取得
const compat = new FlatCompat({
    // 設定互換性を提供するオブジェクトを作成
    baseDirectory: __dirname, // 基本となるディレクトリを設定
    recommendedConfig: js.configs.recommended, // 推奨される設定を指定
    allConfig: js.configs.all, // すべての設定を指定
});

export default defineConfig([
    // ESLint設定の定義をエクスポート
    {
        extends: compat.extends(
            // 推奨設定と他の設定を統合
            "eslint:recommended", // ESLintの推奨設定を拡張
            "plugin:@typescript-eslint/recommended", // TypeScriptの推奨ルールを拡張
            "prettier", // Prettierの設定を統合。Prettierとの競合を避けるため
        ),

        plugins: {
            // 使用するプラグインの設定
            "@typescript-eslint": typescriptEslint, // TypeScript用ESLintプラグイン
            unicorn, // ユニコーンプラグイン
            prettier, // Prettierプラグイン
        },

        rules: {
            // 特定のESLintルールの設定
            "@typescript-eslint/naming-convention": [
                // TypeScriptの命名規則のルール
                "error", // ルール違反時にエラーを表示
                {
                    selector: "variable", // 変数名の規則
                    format: ["camelCase"], // camelCase形式で命名
                },
                {
                    selector: "function", // 関数名の規則
                    format: ["camelCase"], // camelCase形式で命名
                },
                {
                    selector: "class", // クラス名の規則
                    format: ["PascalCase"], // PascalCase形式で命名
                },
                {
                    selector: "interface", // インターフェース名の規則
                    format: ["PascalCase"], // PascalCase形式で命名
                    prefix: ["I"], // 接頭辞として"I"を追加
                },
                {
                    selector: "property", // プロパティ名の規則
                    format: ["camelCase"], // camelCase形式で命名
                },
                {
                    selector: "classProperty", // クラスプロパティ名の規則
                    format: ["camelCase"], // camelCase形式で命名
                },
                {
                    selector: "classMethod", // クラスメソッド名の規則
                    format: ["camelCase"], // camelCase形式で命名
                },
                {
                    selector: "typeAlias", // 型エイリアス名の規則
                    format: ["PascalCase"], // PascalCase形式で命名
                },
                {
                    selector: "enum", // 列挙型名の規則
                    format: ["PascalCase"], // PascalCase形式で命名
                },
                {
                    selector: "enumMember", // 列挙型メンバー名の規則
                    format: ["UPPER_CASE"], // UPPER_CASE形式で命名
                },
            ],
        },
    },
    {
        files: ["**/*.ts", "**/*.tsx"], // TypeScriptファイル（.ts, .tsx）に対する設定
        rules: {
            "unicorn/filename-case": [
                // ユニコーンプラグインによるファイル名の命名規則
                "error", // ルール違反時にエラーを表示
                {
                    case: "camelCase", // camelCase形式のファイル名を強制
                },
            ],
        },
    },
]);
```

:::note warn
上記コードをコピペすると、１行目の`/* eslint-disable */`が消える場合があるので、その場合は再度１行目に貼り付けてください
:::

### Prettier設定ファイル
Prettier設定ファイルは以下の通りです。
```json:.prettierrc
{
    "$comment1": "文字列の引用符としてダブルクオートを使用",
    "doubleQuote": true,
    "$comment2": "配列やオブジェクトの最後の要素にもカンマを付ける",
    "trailingComma": "all",
    "$comment3": "インデントの幅を4スペースに設定",
    "tabWidth": 4
}
```

### VSCodeのsettings.jsonの編集
VSCodeのsettings.jsonに以下内容を追記します。
```settings.json
{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },
  "eslint.format.enable": false,
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
}
```

# 動作確認
一部動作確認をしていきましょう。

:::note warn
VSCode内でエラーが表示されない場合(`eslint.config.mjs`が読み込まれていない場合)は、拡張機能のESLinstを再起動してみてください。
:::

### ファイル名チェック
ファイル名が`camelCase`になっていないのでエラーが出ていますね。
![画像5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/c9382b31-0703-497b-8dd8-bebeb21c5c90.png)


### class名チェック
class名が`PascalCase`になっていないのでエラーが出ていますね。
![画像6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/5e9143f9-c9fa-4830-a78c-e3eb89995817.png)

### 変数名チェック
変数名が`camelCase`担っていないのでエラーが出ていますね。
![画像7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/5175a7bb-480f-4b8c-b646-a9871db0d65c.png)

### 関数名チェック
関数名が`camelCase`担っていないのでエラーが出ていますね。
![画像8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/9cb4d7f6-4976-4319-947c-700df3b15786.png)

### インターフェース名チェック
インターフェース名が`I`から始まってないのでエラーが出ていますね。
![画像9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/9f9dff2a-246e-4f1e-a94b-e9659d74f4a7.png)

`I`から始まっていても、`PascalCase`になっていないのでエラーが出ていますね。
![画像10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/5fa11ca2-6df7-430b-8a28-5b1a797c0471.png)


# まとめ
今回はAWS CDK TypeScriptの命名規則ベストプラクティスをチェックするためのESLintの設定についてまとめました。こちらの記事が誰かの役に立てば幸いです。

# 参考資料
### リファレンス
* [AWS 規範ガイダンス - TypeScript AWS CDK で を使用して IaC プロジェクトを作成するためのベストプラクティス - ](https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/best-practices-cdk-typescript-iac/typescript-best-practices.html)
* [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules)
### ブログ
* [ESLintでTypeScriptの変数の名付け規則をチェックしよう！](https://dev.classmethod.jp/articles/shuntaka9576-check-eslint/#toc-)
* [【完全版】eslint&prettier ~vscode~](https://zenn.dev/sawao/articles/6ad32596a82174#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB)
* [[解説] npm install コマンドの解説！](https://massu-engineer.com/npm-install-option/#i-5)
* [キャメルケースとは？種類やメリット・注意点をわかりやすく解説](https://it.webcli.jp/topics/camelcase/)