Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

eslint-plugin-import を使ってディレクトリ単位でアクセス制限を敷く

TypeScript を使っていて、「この関数、他のディレクトリからはアクセスして欲しくないんだけどテストのために export しなきゃ行けないな…」みたいなケースありませんか?実は、ESLint でうまく設定してやると解決できます!今回はその方法を紹介します。

お急ぎ開け口

  1. eslint-plugin-import をインストール
  2. 以下を参考に print-allowed-dir.sh を書く
  3. print-allowed-dir.sh を使って eslint の設定を書く(下記参照)

デモとして以下のリポジトリを作成したので、こちらもご覧ください。

github.com

eslint-plugin-import とは?

eslint-plugin-import は外部モジュールへのアクセスの仕方を設定するプラグインです。(外部モジュールとは、同一階層でないディレクトリとだいたい同義です)

たとえば、以下のように eslint-plugin-import の設定をすると、今回やりたいことが実現できます。

// .eslintrc.js
module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:import/typescript',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 12,
    sourceType: 'module',
  },
  plugins: ['@typescript-eslint', 'import'],
  rules: {
    // アクセス可能なファイルを glob を使って列挙する
    'import/no-internal-modules': [
      'error',
      {
        allow: ['**/dir/*', '**/dir-including-index/index.ts'],
      },
    ],
  },
};
src/
  - dir/
    - reachable.ts
  - dir-including-index/
    - index.ts
    - internal.ts

上の設定では、このディレクトリ構成に対して

  • dir/reachable.ts にはどこからでもアクセス可能
  • dir-including-index/internal.ts は dir-including-index/index.ts のみアクセス可能(同一階層のため)
  • dir-including-index/index.ts にはどこからでもアクセス可能

となります。うまくやりたいことが実現できました。

しかし、いちいち各ディレクトリへのアクセス制限を手動で書くのは馬鹿馬鹿しいですよね。というわけで自動化します。

ディレクトリ列挙を自動化する

もちろん、いちいち index.ts が含まれたフォルダを探して設定をしていたのでは面倒です。Shell Script を書いて自動化してしまいましょう。

次の Shell Script を print-allowed-dir.sh と名付けて保存してください。実行権限の付与をお忘れなく。

#!/bin/bash
list_directories() {
  find ./src -type d
}
list_index_files() {
  find ./src -type f -name 'index\.*'
}
subtract() {
  diff "$1" "$2" | grep '^< ' | sed -e 's/< //'
}

(
  subtract <(list_directories) <(list_index_files | xargs -I{} dirname {} | uniq) | sed -e 's/$/\/*/'
  list_index_files
)  | sed -e 's/\.\/src/**/' | sed -e '/\*\*\/\*/d'

あとはこれを使って設定できるよう .eslintrc.js を書き換えます。

// print-allowed-dir.sh を実行すると各ディレクトリに対して
//  - **/dir-with-index/index.ts
//  - **/dir-without-index/*
// が出力されるので、eslint-plugin-import へ渡す
const { execSync } = require('child_process');
const allowList = `${execSync('./print-allowed-dir.sh')}`
  .replace(/\n$/, '')
  .split('\n');

module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:import/typescript', // eslint-plugin-import の設定を読み込む
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 12,
    sourceType: 'module',
  },
  plugins: ['@typescript-eslint', 'import'], // eslint-plugin-import を読み込む
  rules: {
    'import/no-internal-modules': [
      'error',
      {
        allow: allowList,
      },
    ],
  },
};

(この記事は同様のことをしてくれるESLintプラグインが見当たらなくて書いたので、同じことをしてくれるプラグインがあれば紹介してほしいです)

おまけ: testディレクトリからはすべてのファイルにアクセス可能とする

ESLint なので、overrides をうまく使うことで様々なことができます。一例として、srcディレクトリ以下のファイルに対しては上の制限を敷くが、testディレクトリには敷かないということをやってみます。

const { execSync } = require('child_process');
const allowList = `${execSync('./print-allowed-dir.sh')}`
  .replace(/\n$/, '')
  .split('\n');

module.exports = {
  // (省略)
  overrides: [
    {
      files: ['src/**/*'],
      rules: {
        'import/no-internal-modules': [
          'error',
          {
            allow: allowList,
          },
        ],
      },
    },
    {
      files: ['test/**/*'],
      rules: {
        'import/no-internal-modules': 'off',
      },
    },
  ],
};

これで、src以下のファイルたちにはアクセス制限を掛けられつつ、test からは自由に private 関数へアクセスできます。