dependency-cruiserを使って依存関係を検証し潜在的なバグを潰す


[sverweij/dependency-cruiser: Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.](https://github.com/sverweij/dependency-cruiser)

![](https://raw.githubusercontent.com/sverweij/dependency-cruiser/master/doc/assets/sample-dot-output.png)

このツールが何を行うかというと、依存関連を検証してくれる。
例えばフロントエンドのコードの規模が20万行だとかそんな規模だとすると、使われていないファイルだとか相互依存になっているファイルが見つかることがままある。
このツールを使うことでそれらの発見が容易になる。

```
yarn add --dev dependency-cruiser
```

設定ファイルが以下のコマンドで生成できる。

```
npx depcruise --init
```

デフォルトでは以下のようなファイルが生成される。

```json
{
  "forbidden": [
    {
      "name": "not-to-test",
      "comment": "Don't allow dependencies from outside the test folder to test",
      "severity": "error",
      "from": {
        "pathNot": "^(test|spec)"
      },
      "to": {
        "path": "^(test|spec)"
      }
    },
    {
      "name": "not-to-spec",
      "comment": "Don't allow dependencies to (typescript/ javascript/ coffeescript) spec files",
      "severity": "error",
      "from": {},
      "to": {
        "path": "\\.spec\\.(js|ts|ls|coffee|litcoffee|coffee\\.md)$"
      }
    },
    {
      "name": "no-circular",
      "severity": "warn",
      "comment": "Warn in case there's circular dependencies",
      "from": {},
      "to": {
        "circular": true
      }
    },
    {
      "name": "no-orphans",
      "severity": "info",
      "comment": "Inform in case there's orphans hiding in the code base",
      "from": {
        "orphan": true,
        "pathNot": "\\.d\\.ts$"
      },
      "to": {}
    },
    {
      "name": "no-deprecated-core",
      "comment": "Warn about dependencies on deprecated core modules.",
      "severity": "warn",
      "from": {},
      "to": {
        "dependencyTypes": [
          "core"
        ],
        "path": "^(punycode|domain|constants|sys|_linklist)$"
      }
    },
    {
      "name": "no-deprecated-npm",
      "comment": "These npm modules are deprecated - find an alternative.",
      "severity": "warn",
      "from": {},
      "to": {
        "dependencyTypes": [
          "deprecated"
        ]
      }
    },
    {
      "name": "not-to-unresolvable",
      "comment": "Don't allow dependencies on modules dependency-cruiser can't resolve to files on disk (which probably means they don't exist)",
      "severity": "error",
      "from": {},
      "to": {
        "couldNotResolve": true
      }
    },
    {
      "name": "not-to-dev-dep",
      "severity": "error",
      "comment": "Don't allow dependencies from src/app/lib to a development only package",
      "from": {
        "path": "^(src|app|lib)",
        "pathNot": "\\.spec\\.(js|ts|ls|coffee|litcoffee|coffee\\.md)$"
      },
      "to": {
        "dependencyTypes": [
          "npm-dev"
        ]
      }
    },
    {
      "name": "no-non-package-json",
      "severity": "error",
      "comment": "Don't allow dependencies to packages not in package.json (except from within node_modules)",
      "from": {
        "pathNot": "^node_modules"
      },
      "to": {
        "dependencyTypes": [
          "unknown",
          "undetermined",
          "npm-no-pkg",
          "npm-unknown"
        ]
      }
    },
    {
      "name": "optional-deps-used",
      "severity": "info",
      "comment": "nothing serious - but just check you have some serious try/ catches around the import/ requires of these",
      "from": {},
      "to": {
        "dependencyTypes": [
          "npm-optional"
        ]
      }
    },
    {
      "name": "peer-deps-used",
      "comment": "Warn about the use of a peer dependency (peer dependencies are deprecated with the advent of npm 3 - and probably gone with version 4).",
      "severity": "warn",
      "from": {},
      "to": {
        "dependencyTypes": [
          "npm-peer"
        ]
      }
    },
    {
      "name": "no-duplicate-dep-types",
      "comment": "Warn if a dependency you're actually using occurs in your package.json more than once (technically: has more than one dependency type)",
      "severity": "warn",
      "from": {},
      "to": {
        "moreThanOneDependencyType": true
      }
    }
  ],
  "options": {
    "doNotFollow": "node_modules"
  }
}
```

とりあえず、相互に依存したものをチェックするのだけやってみる。
dependency-cruiser.jsonを以下のように書き換える。
no-circularだけ有効化した形だ。

```
{
  "name": "no-circular",
  "severity": "error",
  "comment": "Warn in case there's circular dependencies",
  "from": {},
  "to": {
    "circular": true
  }
}
```

そして大袈裟に書くが以下のようなtsを用意する。
デフォルトでTypeScriptをサポートしているので便利。

src/main.ts

```ts
import { sub } from './sub'

export function main() {
  return 'main'
}

sub()
```

src/sub.ts

```ts
import { main } from './main'

export function sub() {
  return 'sub'
}

main()
```

実行

```
npx depcruise -v .dependency-cruiser.json src

  error no-circular: src/main.ts → src/sub.ts
  error no-circular: src/sub.ts → src/main.ts

✖ 2 dependency violations (2 errors, 0 warnings). 2 modules cruised.
```

![スクリーンショット 2018-09-04 20.42.54.png](https://qiita-image-store.s3.amazonaws.com/0/15319/ee3d864f-ba4f-91d2-4190-ffc2b23e5880.png)


このように相互依存が明らかになって、潜在的なバグを減らすことができる。

また`no-orphans`を有効にすればどこからも参照されていない独立したファイルを見つけることも簡単だ。

## まとめ
小中規模であれば特に効果を発揮しないかも知れないが、巨大なプロジェクトに出会ったときに非常に役に立つツールだと思う。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">sverweij/dependency-cruiser: Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD. <a href="https://t.co/JhJzYotJYQ">https://t.co/JhJzYotJYQ</a> これを使って悲しみを減らす作業をしてる</p>&mdash; あかめ@歴史的政治的経緯.js (@akameco) <a href="https://twitter.com/akameco/status/1034261205471252481?ref_src=twsrc%5Etfw">2018年8月28日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>