📚

rdlabo/eslint-plugin-rules:私が欲しかったAngularプロジェクトのESLintプラグイン

に公開

私が欲しかったAngularプロジェクトのESLintプラグイン

チームでAngular開発をしていると、「この実装ミス自動検出してほしいな」「この書き方を強制してくれないかな」と悩むことありますよね。私の悩みを解決するために @rdlabo/eslint-plugin-rules を作りました。私がプロジェクトで必要なルールだったルールまとめて、公開した形です。

例えば、Signalパターンへの移行をしていると、マイグレーションのミスって結構起こりがちなんですよね。ちなみに私が見逃しがちなのはこのパターンです。

name = signal<string | undefined>('Angular');

// ❌ WritabeSignal<string>を確認してしまってる
if (name) { ... }

// ✅ 正しくはこちら
if (name()) { ... }

nameWritabeSignal<string> なので、条件文でみると値が空やundefinedでも必ず true を返します。構文的には正しいのでコンパイルエラーも起こさないし、テストや動作確認でようやく気づくレベルの不具合です。まじしんどかったので、これを検出するルールもつくりました。あります。ルール集なので、チームやプロジェクトによって合う・合わないはあると思いますが、もし使えそうなルールがあったら使ってもらえると嬉しいです。

https://github.com/rdlabo-team/eslint-plugin-rules

利用可能なルール

このプラグインでは、以下のカスタム ESLint ルールが提供されています:

@rdlabo/rules/deny-constructor-di

コンポーネントやサービスのコンストラクター内での依存性注入(DI)を防ぐルールです。これは、コンストラクターDIからAngularの inject 関数を使ったDIへの移行を促すためのものです。

// ❌ 従来の書き方
constructor(private http: HttpClient) {}

// ✅ 推奨される書き方
private http = inject(HttpClient);

以前はauto fix機能もあったのですが、 ng generate @angular/core:inject コマンドがリリースされたので、auto fix機能は削除しました。

@rdlabo/rules/signal-use-as-signal

Angular Signals の適切な使用法を検証するルールです。Signals の値を直接ミューテートしたり、不正な方法で Signal にアクセスしたりするコードを検出します。

// ❌ 不正な使用
this.user().name = data.name;
this.users().push(user);
if (this.user) { /* ... */ }

// ✅ 正しい使用
this.user.update(user => ({ ...user, name: data.name }));
this.users.update(users => [...users, user]);
if (this.user()) { /* ... */ }

以前からこのルールはあったのですが、v20.0.0でこのルールを大幅に見直し、auto fix機能も利用可能にしました。

@rdlabo/rules/signal-use-as-signal-template

Angular Signals をテンプレート内で正しく使用することを強制するルールです。Signal の値にアクセスする際は、テンプレートでも Signal 関数を呼び出す必要があります。

<!-- ❌ 不正な使用 -->
{{ count }}
{{ user.name }}
@if (user) { /* ... */ }

<!-- ✅ 正しい使用 -->
{{ count() }}
{{ user().name }}
@if (user()) { /* ... */ }

v20.0.0で追加した新しいルールです。auto fix機能は現在利用できません( templateUrl を辿るのでLint対象ファイルでないことから仕様上auto fixできなかった)

@rdlabo/rules/component-property-use-readonly

クラスプロパティに対して readonly 修飾子の使用を強制するルールです。特に Angular コンポーネントのプロパティに対して適用されます。

// ❌ readonly が付いていない
private users: User[];
count: number;
name: string;

// ✅ readonly を追加
private readonly users: User[];
readonly count: number;
readonly name: string;

これは、以下で紹介した Zoneless アプリケーションへの移行戦略に含まれる「全てのコンポーネントプロパティを readonly にする」ことをサポートするためのルールです。v20.0.0で追加された新しいルールで、オートフィックス機能が利用可能です。

https://zenn.dev/rdlabo/articles/c6623c6ccc16dd

@rdlabo/rules/deny-soft-private-modifier

ソフトプライベート修飾子の使用を防ぐルールです。明示的なアクセシビリティ修飾子を推奨します。

// ❌ ソフトプライベート修飾子
private http = inject(HttpClient);

// ✅ ハードプライベート修飾子
#http = inject(HttpClient);

オートフィックス機能も利用可能です。

@rdlabo/rules/deny-element

特定の HTML 要素の使用を制限するルールです。設定によって禁止したい要素を指定できます。主にIonicプロジェクトで、モーダルやポップオーバー要素をテンプレートで直接使用することを禁止し、コントローラーからの使用を推奨するために使っています。

<!-- ❌ テンプレートでの直接使用を禁止(Ionicの場合) -->
<ion-modal>
  <ion-content>...</ion-content>
</ion-modal>

<!-- ✅ コントローラーからの使用を推奨 -->
<!-- TypeScriptでModalControllerを使用 -->

@rdlabo/rules/deny-import-from-ionic-module

Ionicプロジェクトで @ionic/angular からの直接インポートを防ぐルールです。より粒度の細かいパスからインポートすることを推奨します。

オートフィックス機能が利用可能です。

@rdlabo/rules/implements-ionic-lifecycle

Ionicプロジェクトでライフサイクルフック(例: ionViewWillEnter など)をコンポーネントで使用する際に、対応する TypeScript インターフェース(例: IonViewWillEnter)をクラスが適切に実装していることを保証するルールです。

// ✅ 正しい実装(Ionicプロジェクトの場合)
export class MyPage implements IonViewWillEnter {
  ionViewWillEnter() {
    // ライフサイクル処理
  }
}

v20.0.0で機能を強化して、ライフサイクルインターフェースの実装を自動的に検出して修正したり、空のクラスから不要なインターフェースを削除したりできるようになりました。オートフィックス機能が利用可能です。

インストール方法と設定方法

プラグインのインストールは簡単です:

npm install @rdlabo/eslint-plugin-rules --save-dev

ただし、注意点があります。もしプロジェクトに angular-eslint パッケージがインストールされていない場合は、このプラグインをインストールする前に angular-eslint パッケージを先にインストールする必要があります。

# angular-eslintが未インストールの場合は先にインストール
ng add angular-eslint

プロジェクトにインストールした後、ESLint の設定ファイル(通常 eslint.config.js)にプラグインと使用したいルールを追加します。設定は、主に TypeScript ファイル(*.ts)と HTML ファイル(*.html)に対して行います。

const rdlabo = require('@rdlabo/eslint-plugin-rules');

module.exports = tseslint.config(
  {
    files: ['*.ts'],
    plugins: {
      '@rdlabo/rules': rdlabo,
    },
    rules: {
      '@rdlabo/rules/deny-constructor-di': 'error',
      '@rdlabo/rules/deny-import-from-ionic-module': 'error',
      '@rdlabo/rules/implements-ionic-lifecycle': 'error',
      '@rdlabo/rules/deny-soft-private-modifier': 'error',
      '@rdlabo/rules/signal-use-as-signal': 'error',
      '@rdlabo/rules/signal-use-as-signal-template': 'error',
      '@rdlabo/rules/component-property-use-readonly': 'error',
    },
  },
  {
    files: ['*.html'],
    plugins: {
      '@rdlabo/rules': rdlabo,
    },
    rules: {
      // Ionicを使用している場合のみ設定
      '@rdlabo/rules/deny-element': [
        'error',
        {
          elements: [
            'ion-modal',
            'ion-popover',
            'ion-toast',
            'ion-alert',
            'ion-loading',
            'ion-picker',
            'ion-action-sheet',
          ],
        },
      ],
    },
  }
);

まとめ

@rdlabo/eslint-plugin-rules は、Angular アプリケーションのコード品質と保守性を向上させるためのカスタム ESLint ルールの集合体です。特に v20.0.0 リリースでは、Angular Signals のサポートが強化され、Angular v20 で推奨される Zoneless アプリケーションへの移行を支援するための新しいルールが導入されました。

これらのルールは、Angular 開発における特定のベストプラクティスやアンチパターンを防ぐのに役立ち、多くの場合オートフィックス機能も提供されています。特に以下のような場面で威力を発揮します:

  • Angular Signalsの適切な使用法の強制
  • コンストラクターDIから inject() 関数への移行支援
  • Zoneless変更検知への移行準備(readonly修飾子の強制)
  • TypeScriptのアクセシビリティ修飾子の適切な使用

プロジェクトはオープンソースであり、コントリビューションも受け付けています。Ionicプロジェクトでも追加のルールが利用できるため、幅広いAngularプロジェクトで活用できます。
Angular開発でコード品質を向上させたい方は、ぜひ導入を検討してみてください。

https://github.com/rdlabo-team/eslint-plugin-rules

それでは、また。

Discussion