コード分析ツール
August 16, 2026 · View on GitHub
プロジェクトでは3つのコード分析ツールを使用して、コード品質とアーキテクチャの健全性を維持している。
Knip(デッドコード検出)
未使用のファイル、エクスポート、依存関係を検出する静的解析ツール。
検出対象
- 未使用ファイル(どこからもimportされていないモジュール)
- 未使用エクスポート(exportされているが使われていないシンボル)
- 未使用依存関係(package.jsonに記載されているが使われていないパッケージ)
設定
設定ファイル: knip.json
モノレポの各ワークスペースごとにエントリーポイントと除外設定を定義:
- app: Next.jsの規約ファイル(
page.tsx,layout.tsx,route.ts,instrumentation.ts等)をエントリーポイントとして自動認識。i18n/request.ts等の内部ファイルはignore対象 - packages/ui:
ui/,forms/,display/,layouts/,providers/,hooks/,utils/配下のファイルがエントリーポイント。Storybookファイル・テストファイルは除外 - packages/core: 全
.tsファイルがエントリーポイント(テストファイルは除外) - packages/database: Prisma関連の依存関係をignore
ignoreExportsUsedInFile: trueにより、同一ファイル内で使用されているexportは未使用扱いしない。
コマンド
pnpm knip # デッドコード検出を実行
jscpd(コード重複分析)
コピー&ペーストされた重複コードを検出するツール。
設定
設定ファイル: .jscpd.json
| 項目 | 値 |
|---|---|
| 重複率閾値 | 10% |
| 対象パターン | **/*.{ts,tsx} |
| 分析パス | app/src, packages/ui, packages/core |
除外対象:
node_modules,.next,dist,.storybook-static,coverage- テストファイル(
*.test.ts) - 生成ファイル(
app/src/generated/**)
コマンド
pnpm jscpd # コンソールに重複レポートを出力
pnpm jscpd:json # JSON形式のレポートを ./jscpd-report/ に出力
pnpm jscpd:summary # サマリーを jscpd-summary.txt に出力
CI連携
- PR時:
jscpd.yaml— PRごとに重複率を分析し、結果をPRコメントとして投稿。閾値超過時は警告を表示。レポートはArtifactとして90日間保存 - 月次レポート:
update-reports.yaml— 毎月1日にjscpd-summary.txtを自動更新するPRを作成
dependency-cruiser(依存関係分析)
モジュール間の依存関係を分析し、アーキテクチャルールへの違反を検出するツール。
設定
設定ファイル: .dependency-cruiser.cjs
ドメイン境界ルール(severity: error)
packages/core内の4ドメイン間の相互importを禁止:
| ルール名 | from | to(禁止) |
|---|---|---|
boundary-core-articles | packages/core/articles/ | books, notes, images |
boundary-core-books | packages/core/books/ | articles, notes, images |
boundary-core-notes | packages/core/notes/ | articles, books, images |
boundary-core-images | packages/core/images/ | articles, books, notes |
各ドメインはshared-kernel経由でのみ共通機能を利用できる。
汎用ルール
| ルール名 | severity | 検出内容 |
|---|---|---|
no-circular | warn | 循環依存 |
no-orphans | warn | 孤立モジュール(どこからも参照されていないファイル) |
no-deprecated-core | warn | 非推奨のNode.jsコアモジュールの使用 |
not-to-deprecated | warn | 非推奨npmパッケージへの依存 |
no-non-package-json | error | package.jsonに未記載のnpmパッケージへの依存 |
not-to-unresolvable | error | 解決不能なモジュールへの依存 |
no-duplicate-dep-types | warn | dependencies/devDependencies両方に存在するパッケージ |
not-to-spec | error | テストファイルへの依存(プロダクションコードから) |
not-to-dev-dep | error | devDependenciesへのプロダクションコードからの依存 |
コマンド
pnpm deps:check # 依存関係ルールのチェック(全ルール適用)
pnpm deps:circular # 循環依存の検出(テキスト出力)
pnpm deps:graph # 依存関係グラフをmermaidとして出力(dependency-graph.md)
deps:graphはmermaid形式で出力するためGraphviz不要。生成物(dependency-graph.md)はアーキテクチャ図としてコミット管理し、GitHub・VS Code上でネイティブ描画する。月次で自動再生成されるため(CI連携を参照)、ローカルでは構成変更時にpnpm deps:graphで更新する。
グラフは「Clean Architectureの層境界・cross-domain禁止が守られているか」を一目で確認する用途のため、層×ドメイン単位まで粗く集約している:
--collapse '<regex>'で集約。core/application-services/infrastructuresはドメイン(articles/books/images/notes等)ごとのノードを残し(cross-domain依存の可視化)、app/common/components/loadersや各パッケージ(ui/database等)は1ノードに集約する。ドメイン内部のファイル詳細は描画しない。--include-only '^(app/src|packages)/...'でグラフ出力のみを絞り込み(ルール検査には不適用)、node_modules等の外部依存とvitest設定・*.stories.*などのノイズノードを除外する。
CI連携
- PR時:
depcruise.yaml— dependency-cruiser-report-actionを使用し、依存関係分析レポートをPRコメントとして投稿 - 月次レポート:
update-reports.yaml— 毎月1日にdependency-graph.mdを再生成し、自動更新PRを作成
Lint/Format: oxlint主体 + コンテンツESLint、oxfmt(2026-08)
TypeScript/JavaScript は oxlint に一本化した状態を維持し、oxlint が扱わない YAML/JSON/Markdown のみ ESLint で検査する。oxlint 設定は .oxlintrc.json(JSONC)、コンテンツ lint は eslint.config.js、フォーマットは .oxfmtrc.json。pnpm lint / pnpm lint:fix は両リンターを順に実行する。
役割分担:
- oxlint: 旧 ESLint の全ルール。typescript-eslint strict+stylistic type-checked(type-aware, tsgolint)、
@next/*→nativenextjs、@vitest/*→nativevitest、eslint-plugin-regexp/eslint-plugin-storybook(JS plugin)、Prisma raw-SQL ガード(eslint-js/no-restricted-syntaxviaoxlint-plugin-eslint)。@eslint-reactの明示ルールは native へ: use-state→react/hook-use-state、jsx-no-useless-fragment→react/jsx-no-useless-fragment、dom-no-dangerously-set-innerhtml→react/no-danger、no-array-index-key→react/no-array-index-key。set-state-in-effect はeslint-plugin-react-hooks(aliasreact-hooks-js)。 - ESLint:
eslint-plugin-yml、eslint-plugin-jsonc、@eslint/markdownで YAML/JSON/JSONC/JSON5/Markdown を検査。ロックファイルと生成物は除外。 - dependency-cruiser: Clean Architecture 層境界(
eslint-plugin-boundariesから移植。oxlint の JS plugin はsettingsの boundaries/* キーを受け付けず動作不可のため)。.dependency-cruiser.cjsのboundary-*ルール(allow-list を deny-list に翻訳、29 本)。 - oxfmt: format 全般(Prettier互換、タブ・行幅80)+ import 整理(sortImports)+ Tailwind class 並べ替え(sortTailwindcss、
cn/clsx/tv)。旧 Biome の format + organizeImports + useSortedClasses を置換。 - Stylelint: CSS 構文、無効な宣言の組み合わせ、対象ブラウザで未対応の機能、最低限のプロパティ順を検査する。Tailwind CSS v4 のディレクティブと関数は明示的に許可する。
- Markdownlint: 手書き Markdown の構造を検査する。既存違反ファイルは
.markdownlint-cli2.jsoncの baseline に列挙し、修正完了時に該当 entry を削除する。生成 docs、CHANGELOG、agent skills は対象外。 - Secretlint: recommended preset でコミット済みファイルの credential、token、秘密鍵、接続文字列を検査する。ドキュメント上のダミー値は理由付きの局所 disable のみ許可する。
補助 lint のコマンド:
pnpm lint:css # CSS 検査
pnpm lint:css:fix # CSS の安全な自動修正
pnpm lint:md # Markdown 検査(baseline を除く)
pnpm lint:md:fix # Markdown の安全な自動修正
pnpm lint:secret # secret 検査(出力は mask)
CI の supplemental-lint job で3種すべてを検査する。ローカルでは Husky + lint-staged が staged file に対して formatter と安全な fix を直列実行し、最後に Secretlint を実行する。pre-commit の oxlint は高速化と Next.js type generation への非依存化のため非 type-aware とし、完全な oxlint --type-aware は CI で担保する。
- 旧 Biome の base lint は oxlint へ吸収(
categories.correctness="error"+ style ルール移植: noParameterAssign→no-param-reassign、useSelfClosingElements→react/self-closing-comp、useNumberNamespace→unicorn/prefer-number-properties、noUselessElse→no-else-returnほか)。noExplicitAnyはtypescript/no-explicit-any="error"に統一。
新規有効化(旧コメントアウト分): jsx-a11y(oxlint native, error。当初 warn で導入し 2026-05 に error へ昇格)。
移行できなかったもの(oxlint に等価なし):
@eslint-reactの react-x 固有ルール(type-aware で JS plugin 不可): no-unused-props, naming-convention-ref-name, no-context-provider, ほか strict-type-checked 群。本コードベースでは warning 8 件相当の損失(error 損失は 0)。eslint-plugin-tailwindcss: Tailwind v4(tailwind.config.js無し)と非互換のため不採用。class 並べ替えは oxfmtsortTailwindcss(Tailwind v4 native)が担当。- Biome 専用ルール
noUnusedTemplateLiteralとuseSingleVarDeclarator(oxlint にone-var無し)は等価がなく削除(軽微なスタイル系のみ)。