T-CREATOR

ESLint no-restricted-* 活用レシピ集:API 禁止・依存制限・危険パターン封じ込め

ESLint no-restricted-* 活用レシピ集:API 禁止・依存制限・危険パターン封じ込め

プロジェクトの規模が大きくなると、「このライブラリは使わないで欲しい」「この書き方は禁止したい」といったルールを明文化したくなる場面が増えてきます。チームメンバー全員に口頭で伝えても、つい忘れてしまったり、新しく参加したメンバーが知らずに使ってしまったりすることもあるでしょう。

そんなとき、ESLint の no-restricted-* 系ルールが強力な味方になってくれます。API の使用禁止、特定の依存関係の制限、危険な構文パターンの封じ込めなど、プロジェクト固有のルールを自動でチェックできるのです。

本記事では、no-restricted-importsno-restricted-syntaxno-restricted-propertiesno-restricted-globals の 4 つを中心に、実践的な活用レシピをご紹介します。

no-restricted-* ルール早見表

以下の表で、各ルールの概要と主な用途を確認できます。

#ルール名制限対象主な用途設定の難易度
1no-restricted-importsimport 文特定パッケージ・モジュールの禁止★☆☆ 易しい
2no-restricted-syntaxAST ノード(構文)特定の書き方・パターンの禁止★★★ 高度
3no-restricted-propertiesオブジェクトのプロパティ特定オブジェクトのメソッド・プロパティ禁止★★☆ 中級
4no-restricted-globalsグローバル変数グローバル変数の使用禁止★☆☆ 易しい

ルール別の設定キー対応表

各ルールで使える設定キーを一覧にまとめました。

#ルール名主要な設定キー説明
1no-restricted-importsname禁止するパッケージ名
2importNames名前付きインポートのうち禁止する名前
3messageカスタムエラーメッセージ
4no-restricted-syntaxselectorAST セレクター(例:ForInStatement
5messageカスタムエラーメッセージ
6no-restricted-propertiesobject対象オブジェクト名
7property禁止するプロパティ名
8messageカスタムエラーメッセージ
9no-restricted-globals文字列配列禁止するグローバル変数名
10name + messageオブジェクト形式でメッセージ付き

背景

チーム開発における暗黙のルールの課題

チーム開発では、コーディング規約やベストプラクティスを文書化していても、すべてのメンバーが常に意識できるとは限りません。

「Lodash の _.forEach は使わずに配列メソッドを使う」「古い API は使わない」といったルールを口頭やドキュメントで伝えても、レビュー時に指摘されて初めて気づくことが多いでしょう。指摘する側も、毎回同じことを伝えるのは疲れてしまいますし、見落としが発生するリスクもあります。

プロジェクト固有の制約を自動化する必要性

プロジェクトによっては、以下のような固有の制約が存在します。

  • セキュリティ上の理由で特定のライブラリを禁止
  • パフォーマンスのために特定の構文を避ける
  • レガシー API から新しい API への移行を促進
  • モジュールの依存関係を一方向に保つアーキテクチャ制約

これらを人力でチェックし続けるのは非効率です。ESLint の no-restricted-* ルールを使えば、こうした制約をコードレベルで自動チェックできます。

次の図は、チーム開発における制約の伝達フローを示しています。

mermaidflowchart LR
  rule["ルール策定"] -->|ドキュメント化| doc["README/<br/>Wikiに記載"]
  doc -->|読む| dev["開発者"]
  dev -->|コーディング| code["コード"]
  code -->|レビュー| review["コードレビュー"]
  review -->|指摘| dev

  rule -.->|ESLint設定| eslint["no-restricted-*<br/>ルール"]
  eslint -->|自動チェック| code
  code -->|違反検出| error["ESLintエラー"]
  error -->|修正| dev

図のように、ドキュメントだけに頼ると「読む → 忘れる → レビューで指摘」のサイクルが発生しますが、ESLint を使えば「書いた瞬間にエラー」として検出できます。

図で理解できる要点

  • ドキュメントだけでは開発者の記憶に頼ることになる
  • ESLint 設定により、コーディング時点で自動的にルール違反を検出できる
  • レビュアーの負担を減らし、機械的なチェックは自動化できる

課題

特定ライブラリの使用を防ぎたい

プロジェクトによっては、以下のような理由で特定ライブラリを禁止したい場合があります。

  • セキュリティホールが発見された古いバージョンのライブラリ
  • バンドルサイズが大きすぎる非推奨ライブラリ
  • チーム標準の代替ライブラリが存在する場合

例えば、Moment.js は非常に人気のあるライブラリでしたが、バンドルサイズの大きさやメンテナンス終了により、現在では Day.js や date-fns への移行が推奨されています。既存コードに Moment.js が混入しないようにしたいケースは多いでしょう。

危険な構文やパターンを排除したい

JavaScript には便利だが危険な構文も存在します。

  • for...in ループはプロトタイプチェーンのプロパティまで列挙してしまう
  • eval() は任意のコードを実行できるためセキュリティリスクが高い
  • arguments は関数型プログラミングの観点で非推奨
  • with 文はスコープを混乱させる

これらの構文を使わせたくない場合、no-restricted-syntax を使えば AST レベルで禁止できます。

特定のメソッドやプロパティを制限したい

オブジェクトのメソッドやプロパティ単位で制限をかけたい場合もあります。

  • Object.prototype への直接的な変更を禁止
  • __proto__ プロパティの使用を避ける
  • Array.prototype.reduce を禁止して可読性を優先

これらは no-restricted-properties で実現できます。

次の図は、no-restricted-* ルールで対応できる課題の全体像を示しています。

mermaidflowchart TD
  problem["プロジェクト固有の制約"] --> import_issue["特定ライブラリ<br/>使用禁止"]
  problem --> syntax_issue["危険な構文<br/>パターン排除"]
  problem --> property_issue["特定メソッド<br/>プロパティ制限"]
  problem --> global_issue["グローバル変数<br/>使用制限"]

  import_issue --> sol1["no-restricted-imports"]
  syntax_issue --> sol2["no-restricted-syntax"]
  property_issue --> sol3["no-restricted-properties"]
  global_issue --> sol4["no-restricted-globals"]

  sol1 --> auto["自動チェック<br/>&エラー検出"]
  sol2 --> auto
  sol3 --> auto
  sol4 --> auto

図で理解できる要点

  • プロジェクト固有の制約は、ライブラリ・構文・プロパティ・グローバル変数の 4 つに分類できる
  • それぞれに対応する no-restricted-* ルールが存在する
  • すべて ESLint による自動チェックで統一的に運用できる

解決策

no-restricted-imports:特定のインポートを禁止

no-restricted-imports ルールを使うと、特定のパッケージやモジュールからのインポートを禁止できます。

基本的な設定方法

ESLint 設定ファイルに以下のように記述します。

javascript// eslint.config.js (Flat Config)
export default [
  {
    files: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'],
    rules: {
      'no-restricted-imports': [
        'error',
        {
          // パッケージ全体を禁止
          name: 'moment',
          message:
            'moment は非推奨です。代わりに date-fns を使用してください。',
        },
      ],
    },
  },
];

上記の設定により、import moment from 'moment' と書いた瞬間に ESLint エラーが表示されます。

名前付きインポートの制限

パッケージ全体ではなく、特定の名前付きインポートのみを禁止することもできます。

javascript// 特定の名前付きインポートのみ禁止
export default [
  {
    files: ['**/*.js'],
    rules: {
      'no-restricted-imports': [
        'error',
        {
          name: 'lodash',
          importNames: ['forEach', 'each'],
          message:
            'lodash の forEach は使わず、Array.prototype.forEach を使ってください。',
        },
      ],
    },
  },
];

この設定では import { forEach } from 'lodash' はエラーになりますが、import { map } from 'lodash' は許可されます。

パターンによる禁止

正規表現パターンを使って、複数のモジュールをまとめて禁止することもできます。

javascript// パターンマッチで禁止
export default [
  {
    files: ['**/*.ts'],
    rules: {
      'no-restricted-imports': [
        'error',
        {
          patterns: [
            {
              group: ['@internal/legacy-*'],
              message: 'レガシーモジュールは使用禁止です。',
            },
          ],
        },
      ],
    },
  },
];

@internal​/​legacy-api@internal​/​legacy-utils など、パターンにマッチするすべてのモジュールが禁止されます。

no-restricted-syntax:特定の構文パターンを禁止

no-restricted-syntax は AST(抽象構文木)のノード型を指定して、特定の構文を禁止できます。

for...in ループの禁止

for...in はプロトタイプチェーンのプロパティも列挙してしまうため、多くのプロジェクトで使用が避けられています。

javascript// for...in ループを禁止
export default [
  {
    files: ['**/*.js'],
    rules: {
      'no-restricted-syntax': [
        'error',
        {
          selector: 'ForInStatement',
          message:
            'for...in は使わず、Object.keys() や for...of を使ってください。',
        },
      ],
    },
  },
];

このルールにより、以下のようなコードはエラーになります。

javascript// ❌ エラー: for...in は禁止されています
for (const key in obj) {
  console.log(key);
}

代わりに以下のような書き方を推奨します。

javascript// ✅ OK: Object.keys を使用
for (const key of Object.keys(obj)) {
  console.log(key);
}

// ✅ OK: Object.entries を使用
for (const [key, value] of Object.entries(obj)) {
  console.log(key, value);
}

eval の禁止

eval() は任意のコードを実行できるため、セキュリティリスクが高い機能です。

javascript// eval を禁止
export default [
  {
    files: ['**/*.js'],
    rules: {
      'no-restricted-syntax': [
        'error',
        {
          selector: 'CallExpression[callee.name="eval"]',
          message:
            'eval() はセキュリティリスクがあるため使用禁止です。',
        },
      ],
    },
  },
];

console.log の禁止(本番コード)

開発中は便利な console.log ですが、本番コードには残したくない場合があります。

javascript// console.log を禁止
export default [
  {
    files: ['src/**/*.js'],
    ignores: ['**/*.test.js', '**/*.spec.js'],
    rules: {
      'no-restricted-syntax': [
        'error',
        {
          selector:
            'CallExpression[callee.object.name="console"][callee.property.name="log"]',
          message:
            '本番コードに console.log を残さないでください。',
        },
      ],
    },
  },
];

テストファイルは ignores で除外しているため、テスト内では console.log を使えます。

no-restricted-properties:特定のプロパティを禁止

オブジェクトのプロパティやメソッド単位で制限をかけられます。

__proto__ の禁止

__proto__ は非標準のプロパティであり、Object.getPrototypeOf()Object.setPrototypeOf() を使うべきです。

javascript// __proto__ を禁止
export default [
  {
    files: ['**/*.js'],
    rules: {
      'no-restricted-properties': [
        'error',
        {
          property: '__proto__',
          message:
            '__proto__ は非標準です。Object.getPrototypeOf() を使ってください。',
        },
      ],
    },
  },
];

Object.prototype への直接的なメソッド呼び出しを禁止

プロトタイプ汚染を避けるため、Object.prototype のメソッドは直接呼び出さず、Object 経由で呼び出すことが推奨されます。

javascript// Object.prototype.hasOwnProperty の直接呼び出しを禁止
export default [
  {
    files: ['**/*.js'],
    rules: {
      'no-restricted-properties': [
        'error',
        {
          object: 'Object',
          property: 'prototype',
          message:
            'Object.prototype への直接アクセスは避けてください。',
        },
      ],
    },
  },
];

以下のように書き換えます。

javascript// ❌ 非推奨
obj.hasOwnProperty('key');

// ✅ 推奨
Object.prototype.hasOwnProperty.call(obj, 'key');

// ✅ より現代的
Object.hasOwn(obj, 'key');

no-restricted-globals:グローバル変数の使用を禁止

ブラウザ環境では多数のグローバル変数が存在しますが、意図せず使ってしまうとバグの原因になります。

event グローバル変数の禁止

古いブラウザでは event というグローバル変数が存在し、イベントハンドラ内で暗黙的に参照できてしまいます。

javascript// event グローバルを禁止
export default [
  {
    files: ['**/*.js'],
    rules: {
      'no-restricted-globals': [
        'error',
        {
          name: 'event',
          message:
            'グローバルの event は使わず、引数で受け取ってください。',
        },
      ],
    },
  },
];

以下のように明示的にイベントオブジェクトを受け取るべきです。

javascript// ❌ グローバル event を暗黙的に参照
button.addEventListener('click', function () {
  console.log(event.target); // event はどこから来た?
});

// ✅ 引数で明示的に受け取る
button.addEventListener('click', function (e) {
  console.log(e.target);
});

name や status などの紛らわしいグローバルを禁止

namestatusclosed などは window のプロパティとして存在するため、変数名として使うと混乱を招きます。

javascript// 紛らわしいグローバルを禁止
export default [
  {
    files: ['**/*.js'],
    rules: {
      'no-restricted-globals': [
        'error',
        'name',
        'status',
        'closed',
        'open',
      ],
    },
  },
];

次の図は、no-restricted-* ルールの適用範囲と、それぞれが AST のどの部分に作用するかを示しています。

mermaidflowchart TB
  code["ソースコード"] --> parser["ESLint Parser"]
  parser --> ast["AST<br/>(抽象構文木)"]

  ast --> import_node["ImportDeclaration<br/>ノード"]
  ast --> syntax_node["各種Statement<br/>ノード"]
  ast --> member_node["MemberExpression<br/>ノード"]
  ast --> identifier_node["Identifier<br/>ノード"]

  import_node --> rule1["no-restricted-imports"]
  syntax_node --> rule2["no-restricted-syntax"]
  member_node --> rule3["no-restricted-properties"]
  identifier_node --> rule4["no-restricted-globals"]

  rule1 --> check["ルールチェック"]
  rule2 --> check
  rule3 --> check
  rule4 --> check

  check --> result["エラー or 警告"]

図で理解できる要点

  • ソースコードは ESLint によって AST(抽象構文木)に変換される
  • no-restricted-* 各ルールは、AST の異なるノードタイプに対して作用する
  • すべてのルールが統一されたチェック機構で動作する

具体例

レシピ 1:Lodash から Native JavaScript への移行

Lodash は便利ですが、現代の JavaScript には多くの機能が標準で備わっています。バンドルサイズ削減のため、Lodash の特定メソッドを禁止します。

設定ファイル

javascript// eslint.config.js
export default [
  {
    files: ['**/*.js', '**/*.ts'],
    rules: {
      'no-restricted-imports': [
        'error',
        {
          name: 'lodash',
          importNames: [
            'forEach',
            'map',
            'filter',
            'reduce',
            'find',
            'some',
            'every',
          ],
          message:
            '配列メソッドはネイティブの Array.prototype を使ってください。',
        },
        {
          name: 'lodash/forEach',
          message:
            'Array.prototype.forEach を使ってください。',
        },
        {
          name: 'lodash/map',
          message: 'Array.prototype.map を使ってください。',
        },
      ],
    },
  },
];

エラーになるコード

javascript// ❌ Lodash のインポートはエラー
import { forEach, map } from 'lodash';
import forEach from 'lodash/forEach';

forEach([1, 2, 3], (num) => console.log(num));

推奨される書き方

javascript// ✅ ネイティブの配列メソッドを使用
[1, 2, 3].forEach((num) => console.log(num));

const doubled = [1, 2, 3].map((num) => num * 2);

レシピ 2:セキュリティリスクのある構文を完全排除

セキュリティ上問題のある構文をすべて禁止します。

設定ファイル

javascript// eslint.config.js
export default [
  {
    files: ['**/*.js'],
    rules: {
      'no-restricted-syntax': [
        'error',
        // eval 禁止
        {
          selector: 'CallExpression[callee.name="eval"]',
          message:
            'eval() はセキュリティリスクがあるため禁止です。',
        },
        // Function コンストラクタ禁止
        {
          selector: 'NewExpression[callee.name="Function"]',
          message:
            'new Function() はセキュリティリスクがあるため禁止です。',
        },
        // with 文禁止
        {
          selector: 'WithStatement',
          message:
            'with 文はスコープを混乱させるため禁止です。',
        },
      ],
    },
  },
];

エラーになるコード

javascript// ❌ すべてエラー
eval('console.log("危険!")');

const fn = new Function('a', 'b', 'return a + b');

with (obj) {
  console.log(property);
}

推奨される書き方

javascript// ✅ 安全な代替手段を使用
// eval の代わりに JSON.parse など安全な方法を使う
const data = JSON.parse('{"key": "value"}');

// Function コンストラクタの代わりに通常の関数を定義
const fn = (a, b) => a + b;

// with の代わりに明示的なプロパティアクセス
console.log(obj.property);

レシピ 3:アーキテクチャ制約の実装(レイヤー依存の禁止)

クリーンアーキテクチャやレイヤードアーキテクチャでは、依存関係の方向を制限する必要があります。

ディレクトリ構造

bashsrc/
├── domain/          # ドメイン層(最も内側)
├── application/     # アプリケーション層
├── infrastructure/  # インフラ層
└── presentation/    # プレゼンテーション層(最も外側)

設定ファイル(domain 層)

ドメイン層は他のどの層にも依存してはいけません。

javascript// eslint.config.js
export default [
  {
    // ドメイン層のファイルに適用
    files: ['src/domain/**/*.ts'],
    rules: {
      'no-restricted-imports': [
        'error',
        {
          patterns: [
            {
              group: [
                '**/application/**',
                '**/infrastructure/**',
                '**/presentation/**',
              ],
              message:
                'ドメイン層は他の層に依存してはいけません。',
            },
          ],
        },
      ],
    },
  },
];

設定ファイル(application 層)

アプリケーション層は infrastructure 層や presentation 層に依存してはいけません。

javascriptexport default [
  {
    files: ['src/application/**/*.ts'],
    rules: {
      'no-restricted-imports': [
        'error',
        {
          patterns: [
            {
              group: [
                '**/infrastructure/**',
                '**/presentation/**',
              ],
              message:
                'アプリケーション層は infrastructure 層や presentation 層に依存できません。',
            },
          ],
        },
      ],
    },
  },
];

エラーになるコード

typescript// ❌ src/domain/user.ts
// ドメイン層が infrastructure 層をインポート
import { UserRepository } from '../infrastructure/UserRepository';

export class User {
  // ...
}

推奨される書き方

typescript// ✅ src/domain/user.ts
// ドメイン層はインターフェースのみ定義
export interface IUserRepository {
  findById(id: string): Promise<User>;
}

export class User {
  // ...
}
typescript// ✅ src/infrastructure/UserRepository.ts
// インフラ層がドメイン層のインターフェースを実装
import { IUserRepository, User } from '../domain/user';

export class UserRepository implements IUserRepository {
  async findById(id: string): Promise<User> {
    // 実装
  }
}

レシピ 4:React プロジェクトでの危険なパターン禁止

React プロジェクトで避けるべきパターンを禁止します。

設定ファイル

javascript// eslint.config.js
export default [
  {
    files: ['**/*.jsx', '**/*.tsx'],
    rules: {
      'no-restricted-syntax': [
        'error',
        // インデックスを key に使うのを禁止
        {
          selector:
            'JSXAttribute[name.name="key"][value.expression.type="MemberExpression"][value.expression.property.name="index"]',
          message:
            'key にインデックスを使わないでください。一意な ID を使用してください。',
        },
      ],
      'no-restricted-properties': [
        'error',
        // findDOMNode の使用を禁止
        {
          object: 'ReactDOM',
          property: 'findDOMNode',
          message:
            'findDOMNode は非推奨です。ref を使ってください。',
        },
        // dangerouslySetInnerHTML の使用を警告
        {
          property: 'dangerouslySetInnerHTML',
          message:
            'XSS のリスクがあります。本当に必要か確認してください。',
        },
      ],
    },
  },
];

エラーになるコード

jsx// ❌ インデックスを key に使用
items.map((item, index) => <div key={index}>{item}</div>);

// ❌ findDOMNode の使用
const node = ReactDOM.findDOMNode(this.refs.myComponent);

// ❌ dangerouslySetInnerHTML の使用
<div dangerouslySetInnerHTML={{ __html: userInput }} />;

推奨される書き方

jsx// ✅ 一意な ID を key に使用
items.map((item) => <div key={item.id}>{item.name}</div>);

// ✅ ref を使用
const myRef = useRef(null);
<div ref={myRef} />;

// ✅ テキストとして安全に表示
<div>{userInput}</div>;

レシピ 5:Node.js プロジェクトでの非推奨 API 禁止

Node.js では、バージョンアップに伴い非推奨になる API が存在します。

設定ファイル

javascript// eslint.config.js
export default [
  {
    files: ['**/*.js'],
    rules: {
      'no-restricted-properties': [
        'error',
        // Buffer コンストラクタの禁止
        {
          object: 'Buffer',
          property: 'constructor',
          message:
            'new Buffer() は非推奨です。Buffer.from() や Buffer.alloc() を使ってください。',
        },
      ],
      'no-restricted-syntax': [
        'error',
        // require.extensions の禁止
        {
          selector:
            'MemberExpression[object.name="require"][property.name="extensions"]',
          message: 'require.extensions は非推奨です。',
        },
      ],
    },
  },
];

エラーになるコード

javascript// ❌ 非推奨の Buffer コンストラクタ
const buf = new Buffer('hello');

// ❌ require.extensions の使用
require.extensions['.custom'] = function () {};

推奨される書き方

javascript// ✅ Buffer.from を使用
const buf = Buffer.from('hello', 'utf-8');

// ✅ Buffer.alloc を使用(初期化が必要な場合)
const buf = Buffer.alloc(10);

まとめ

本記事では、ESLint の no-restricted-* ルール群を使って、プロジェクト固有の制約を自動化する方法をご紹介しました。

4 つのルールの使い分け

  • no-restricted-imports:特定パッケージやモジュールの使用を禁止したいとき
  • no-restricted-syntax:特定の構文パターンを AST レベルで禁止したいとき
  • no-restricted-properties:オブジェクトのメソッドやプロパティ単位で制限をかけたいとき
  • no-restricted-globals:グローバル変数の使用を制限したいとき

導入のメリット

これらのルールを導入することで、以下のメリットが得られます。

  • チームメンバー全員が同じルールを自動的に守れる
  • コードレビューでの指摘回数が減り、レビュアーの負担が軽減される
  • 新しいメンバーもルールを自然に学べる
  • セキュリティリスクやパフォーマンス問題を未然に防げる
  • アーキテクチャの依存関係を強制できる

運用のコツ

no-restricted-* ルールを運用する際は、以下の点に注意してください。

  • メッセージを丁寧に書く:なぜ禁止されているのか、代わりに何を使うべきかを明記する
  • 段階的に導入する:いきなりすべてを禁止せず、重要度の高いものから順に導入する
  • ドキュメントと併用する:ルールの背景や理由を README や Wiki に記載する
  • 定期的に見直す:プロジェクトの成長に合わせてルールを更新する

次のステップ

no-restricted-* ルールに慣れてきたら、以下にも挑戦してみてください。

  • カスタム ESLint ルールの作成:プロジェクト固有のより複雑なルールを実装
  • ESLint プラグインの作成:チーム内で共有できるルールセットをパッケージ化
  • AST Explorer の活用:no-restricted-syntax で使うセレクタを視覚的に理解

プロジェクトのルールを自動化することで、チーム全体の開発効率とコード品質が向上します。ぜひ、本記事のレシピを参考に、あなたのプロジェクトに最適な制約を設定してみてください。

関連リンク