eslint-plugin-import-accessを使ってみる

:::note
## 概要
`defaultImportability: 'package'`のオプションで丸ごとpackage-privateにできます
このオプションがない場合だと`@package`のつけ忘れによる誤exportが起こり得ますがそれを防いでくれます 👍
[dependency-cruiser](https://github.com/sverweij/dependency-cruiser)と[eslint-plugin-strict-depenencies](https://github.com/knowledge-work/eslint-plugin-strict-dependencies)との使い分けを考え直す必要性を感じました
:::

# 1. 背景 🖼️

TypeScirptではexport宣言した変数や関数は全てのファイルからimportできてしまいます。

これにpackage-privateの機能を組み合わせれば外部のディレクトリからimportを制限できたり、モジュール管理がしやすくなると思い調査しました。

https://qiita.com/yamatai12/items/f954e7282444b2b4273b

この記事で`eslint-plugin-import-access`について触れてはいますが、使い込んでみたいと思い今回細かく調査します。

:::note
ESLint,typescript-eslintの設定はこちらで紹介していますのでご覧ください！
[Eslintとtypescript-eslintを手動で設定してみる](https://qiita.com/yamatai12/items/d48fa55bddbfb3c1dc7d)
:::

こちらが紹介するライブラリの作者の方でこのライブラリについて記事も書かれています。

https://zenn.dev/uhyo/articles/eslint-plugin-import-access


:::note
[こちら](https://github.com/yamatai12/use-eslint-plugin-import-access)が今回の一連の作業のリポジトリですので、確認用にお使いください
:::

# 2. eslint-plugin-import-accessとは 🤔

https://github.com/uhyo/eslint-plugin-import-access

ディレクトリ外のファイルからインポートすることを制限するESLintのプラグインです。

# 3. eslint-plugin-import-accessの設定 🚧

## eslint-plugin-import-accessのインストール

```terminal
npm i -D eslint-plugin-import-access
```

## eslintの設定ファイルを修正
- importAccessをimport
- pluginとruleを追加

```eslint.config.js
import typescriptEslintParser from "@typescript-eslint/parser";
import js from "@eslint/js";
import tseslint from 'typescript-eslint';
import importAccess from "eslint-plugin-import-access/flat-config";

export default [
    {languageOptions: {
        parser: typescriptEslintParser,
        parserOptions: {
          project: true,
          sourceType: "module",
        },
      }
    },
    js.configs.recommended,
    ...tseslint.configs.recommended,
    {
        plugins: {
          "import-access": importAccess,
        },
    },
   {
       rules: {
            "import-access/jsdoc": ["error"],
       }
   }
];
```

# 4. エラーになるか試す 🚨

以下のディレクトリ構成で検証します。
```
.
├── call.ts
├── eslint.config.js
├── node_modules
├── package-lock.json
├── package.json
├── sub
│   ├── call.ts
│   └── called.ts
└── tsconfig.json
```

```sub/called.ts
/**
 * @package
 */
export const called = "called";
```

```call.ts
import { called } from "sub/called";
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405182/1ac936c5-7115-a0d2-2167-1f6d5c86e7d0.png)

エラーが表示されました！

# 5. TypeScript Language Service Pluginを有効にする 🧪
```tsconfig.json
{
  "compilerOptions": {
    // ...
    "plugins": [
      // ...
      {
        "name": "eslint-plugin-import-access"
      }
    ]
  }
}
```

VSCodeのコマンドパレット（Ctrl+Shift+P または Cmd+Shift+P）を開き、「TypeScript: Select TypeScript Version」を選択し、「Use Workspace Version」を選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405182/0f36f4f4-9bbe-1949-b637-f836b8512e0a.png)

これで、「Use Workspace Version」を選択します。

すると、先ほどの`call.ts`でsubディレクトリの`called`をimportする分がサジェスチョンで表示されなくなりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405182/44dbe947-1d94-8c08-94db-34d7d0088d3f.png)

:::note
ですが、クイックフィックスではimportできそうです 😅
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405182/b384dfa6-a97d-4f9f-c92b-06ec4de520d2.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405182/34c0507b-7b56-919f-480d-d3d5bda3c36e.png)

# 5. defaultでpackage-privateにできるルール📦

[こちら](https://zenn.dev/yumemi_inc/articles/eslint-plugin-import-access-default-importability)で紹介されていました。

rulesに`defaultImportability: 'package'`を追加します。

```eslint.config.js
{
       rules: {
            "import-access/jsdoc": ["error",
            {
              // defaultImportabilityをpackageに設定
              defaultImportability: 'package',
            }],
       }
   }
```

これをすると、デフォルトでexportされた宣言はpackage-privateになります。

```sub/called.ts
export const called = "called";
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405182/f934e323-addc-f12f-d017-e4b12b1c6333.png)

`@package`のつけ忘れによる誤exportを防げます👍

# 6. 紹介されていた「2種類の抜け穴」について 🫢

こちらのdocの設定を参考に進めます！

https://github.com/uhyo/eslint-plugin-import-access/blob/master/docs/rule-jsdoc.md

## 同じディレクトリ内のindex.tsから再exportすると他のディレクトリからもimport可能
```sub/index.ts
import { called } from "./called";

export { called };
```

```call.ts
import { called } from "sub/index";
```

`inde.ts`に名前を変えてみます。

```sub/inde.ts
import { called } from "./called";

export { called };
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405182/43bec5ca-cc43-462c-4ba7-28e3141e493a.png)

デフォルトの設定だと名前はindexにする必要があるそうです。

必要であれば以下の対応をするとこの抜け穴はなくなります。
`indexLoophole:true`オプションを追加します。

```eslint.config.js
rules: {
            "import-access/jsdoc": ["error",{
              // defaultImportabilityをpackageに設定
              defaultImportability: 'package',
              indexLoophole: false
            }],
       }
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405182/78c40f30-56c3-1ab9-fd6a-8cc7c4778ed3.png)

importできなくなりました。

## ディレクトリ名と同じファイルからはディレクトリの中の package エクスポートをインポートできる

```eslint.config.js
rules: {
            "import-access/jsdoc": ["error",{
              // defaultImportabilityをpackageに設定
              defaultImportability: 'package',
              filenameLoophole:true
            }
            ],
       }
```

```
.
├── call.ts
├── eslint.config.js
├── node_modules
├── package-lock.json
├── package.json
├── sub
│   ├── call.ts
│   └── called.ts
├── sub.ts
└── tsconfig.json
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405182/9c8976f2-e401-4ef1-706c-f22c0ee48dcb.png)

`sub.ts`からsubディレクトリ内のcalledがimportできました。

:::note
これはtestの実装時に有効らしいです！
```test/sub.test.ts
import { called } from "sub/called";
```
importできています。
:::

↑弊社のテックブログで紹介されています！

https://tech.iimon.co.jp/entry/2023/12/07/140000


# 7. default exportでもimportできない 👍

```sub/defaultExport.ts
const exported = 1;
export default exported;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405182/3ffeefa6-e236-b5bf-428a-fcb889150484.png)

# 8. 最後に

- `defaultImportability: 'package'`のオプションで丸ごとpackage-privateにできます
- このオプションがない場合だと`@package`のつけ忘れによる誤exportが起こり得ますがそれを防いでくれます 👍
- [dependency-cruiser](https://github.com/sverweij/dependency-cruiser)と[eslint-plugin-strict-depenencies](https://github.com/knowledge-work/eslint-plugin-strict-dependencies)との使い分けを考え直す必要性を感じました

# 参考 ✨
- [TypeScriptプロジェクトでディレクトリ単位のカプセル化をする](https://tech.iimon.co.jp/entry/2023/12/07/140000)
- [eslint-plugin-import-accessで「そこからそれはimportしないでください！！」を防ぐ](https://zenn.dev/yumemi_inc/articles/eslint-plugin-import-access-default-importability)
- [eslint-plugin-import-accessではじめるディレクトリ単位カプセル化](https://zenn.dev/uhyo/articles/eslint-plugin-import-access)

本記事を読んで頂き、ありがとうございました。
いいねいただけると記事執筆の励みになりますので、参考になったと思われた方は是非よろしくお願い致します🙏

# 他にも依存関係の管理に関する記事を書いています 💪

https://qiita.com/yamatai12/items/fce4104e273981f78807

https://qiita.com/yamatai12/items/90d61e36ee0013556937