jscpd について
開発者やコードレビュー向けに、コード重複検出ツール jscpd の実行方法とレポートの読み方を解説するスキルです。完全一致・名前変更・類似クローンのほか、未使用コード、複雑度、プロジェクト健全性の分析オプションも扱います。
主な機能
- 01220 以上の言語で重複を検出
- 02エージェント向けの簡潔なレポート
- 03完全一致・類似クローンの検出
- 04未使用コードと複雑度の分析
- 05プロジェクト健全性の要約
活用シーン
- 01リポジトリ内の重複コードの発見
- 02名前変更や類似コードの確認
- 03リファクタリング候補の特定
- 04未使用コードと複雑度の集計
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ kucherenko/ jscpd --skill jscpdインストール後、Claude Code で/jscpdを入力して読み込まれたか確認します。
詳細
jscpd
220 以上の言語に対応した、プログラミングソースコードのコピー&ペースト検出ツールです。このスキルを使って jscpd を実行し、出力の内容を確認できます。
クイックスタート
# Run with ai reporter (compact output optimized for agents)
npx jscpd --reporters ai <path>
# With ignore patterns
npx jscpd --reporters ai --ignore "**/node_modules/**,**/dist/**" <path>
# Scope to specific formats
npx jscpd --reporters ai --format "javascript,typescript" <path>
# Second pass, noisier: copies that only differ in names and values (Type-2, "renamed").
# Review each hit before acting on it.
npx jscpd --reporters ai --ignore-identifiers --min-tokens 70 <path>
# Third pass, noisier still: copies with a few edited lines or the same function
# structure (Type-3, "similar"). Keep the settings tight.
npx jscpd --reporters ai --max-gap-lines 1 --similarity <path>
# Where to refactor first: clone list plus a hotspot summary
npx jscpd --reporters ai --summary <path>
# Most complex files only, without clone detection
npx jscpd --complexity --reporters ai <path>
AI レポーターの出力形式
ai レポーターは、エージェントが利用しやすいように、コンパクトでトークン効率の高い形式で出力します。
Clones:
src/ foo.ts:10-25 ~ bar.ts:42-57
src/utils/helpers.ts:100-120 ~ src/utils/other.ts:5-25
src/cart/ basket.js:1-9 ~ cart.js:1-9 (renamed)
src/api/ save-account.js:1-12 ~ save-user.js:1-11 [~0.91 gap]
src/billing/ orders.ts:1-14 ~ refunds.ts:1-15 [~0.85 ast]
---
5 clones · 4.2% duplication
各行はクローンのペアを表します。
- 同じファイル:
path/file.ts 10-25 ~ 45-60(共通パスを1回だけ表示) - 同じディレクトリ:
shared/prefix/ file-a.ts:10-25 ~ file-b.ts:42-57(共通のプレフィックスをまとめて表示) - 異なるパス:
path/a.ts:10-25 ~ path/b.ts:42-57
接尾辞はクローンの種類を示します。接尾辞がない場合は完全一致です。
(renamed):2つのブロックは、識別子名、リテラル値、またはアノテーションのみが異なります(Type-2)。--ignore-identifiers、--ignore-literals、または--ignore-annotationsを指定した場合にのみ表示されます。[~0.91 gap]:最大--max-gap-lines行の一致しない行を挟んで、2つの完全一致クローンがマージされています(Type-3)。数値は、マージされた範囲内で一致するトークンの割合です。[~0.85 ast]:正規化された構文ツリーの部分木が、--similarity以上の割合で一致する2つの関数です(Type-3)。数値はその一致率です。呼び出された関数やメソッドの名前、演算子は一致率に含まれますが、ローカル名やリテラル値は含まれません。そのため、定数が異なる名前変更されたコピーは1となり、別のメソッドを呼び出すコピーはより低い値になります。一方の名前は、もう一方の側の1つの名前に対応している必要があります。2つの名前の役割を入れ替えたコピーは、より低い値になります。
オプション
| オプション | 説明 |
|---|---|
--reporters ai | AI に最適化されたレポーターを使用します(エージェント向けの簡潔なクローン一覧) |
--reporters html | HTML レポートを生成します |
--reporters json | JSON レポートを出力します |
--min-tokens N | 重複と判定する最小トークン数(デフォルト:50) |
--min-lines N | 重複と判定する最小行数(デフォルト:5) |
--threshold N | 重複率が N を超えた場合にエラーで終了します |
--ignore "glob" | 除外するパターン(カンマ区切り) |
--format "list" | 対象を特定の言語に限定します(例:typescript,javascript) |
--cross-formats "groups" | 関連する形式間のクローンを検出します(例:javascript,typescript または js-ts プリセット) |
--ignore-identifiers | すべての識別子を同一として扱い、変数名、関数名、型名だけが異なるブロックを一致させます(Type-2、renamed として報告)。一方の名前はもう一方の一つの名前に対応する必要があるため、データフローが異なる類似コードは対象外です。また、同じテキスト内のフィールドとローカル変数は別々の名前として扱われるため、this.url = url は this.url = link と引き続き一致します(--ignore-identifiers=blind は任意の名前を一致させます) |
--ignore-literals | すべての文字列リテラルを同一として、すべての数値リテラルを同一として扱います(Type-2) |
--ignore-annotations | @ がアノテーションを表す言語では、一致判定の前に @Name / @Name(...) アノテーションとデコレーターを除外します(Type-2) |
--max-gap-lines N | 同じファイルペア内で N 行以下の未一致行を挟むクローンを 1 つの similar クローンに統合します(Type-3、デフォルト:0 = 無効) |
--similarity [RATIO] | 正規化された構文木の部分木を少なくとも RATIO の割合で共有する関数ペアを、similar クローン(Type-3)として報告します。RATIO は (0, 1] で指定し、省略時は 0.8 です。対応言語:JavaScript、TypeScript、Python、Java、Kotlin、Scala、C#、Go、Rust、C、C++、PHP、Ruby、Swift、Clojure。テストファイルは対象外です |
--min-nodes N | --similarity の判定対象となる関数の正規化構文木に必要な最小ノード数(デフォルト:20) |
--summary | トークン数、行数、サイズ、複雑度の上位ファイル/フォルダーと重複の割合を示すコードベースの概要を追加します |
--summary-top N | 概要の各上位リストに表示する項目数(デフォルト:10) |
--summary-by metric | 概要の並べ替え基準:tokens、lines、size、complexity(デフォルト:tokens) |
--kind list | 指定したクローン種別のみを報告します:exact、renamed、similar、gap、ast(検出器は有効化されません) |
--complexity | クローン検出を行わず、複雑度の表のみを表示します(レポーター:console、ai、json) |
--dead-code | 未使用のファイル、エクスポート、モジュール内限定の宣言、インポートを検出します — JavaScript、TypeScript、JSX、TSX、Vue、Svelte、Astro、Python。各検出結果には 0~100 の信頼度スコアが付与されます |
--dead-code-categories list | --dead-code/--dashboard の対象カテゴリを限定します:unused-file、unused-export、unused-symbol、unused-import |
--min-confidence N | --dead-code/--dashboard の検出結果に適用する信頼度の下限(0~100、デフォルト:60) |
--entry "glob" | package.json/pyproject.toml/フレームワークの規約から推定されるものに加え、デッドコード解析用の追加エントリーポイントを指定します |
--include-tests | デッドコード解析で、テストのみでの使用も「使用中」としてカウントします(デフォルトでは除外) |
--include-entry-exports | エントリーファイル自身の未使用エクスポートも検出します(デフォルトでは除外:エクスポートは公開 API です) |
--dashboard | 1 画面に健全性バッジ、プロジェクトの規模と最大のコードファイル、重複、複雑度、デッドコードを表示します(JS/TS/Python)。-r json は jscpd-dashboard.json を書き出します |
--health | プロジェクトの健全性バッジのみを表示します:重複、デッドコード、複雑度に基づく 0~100 のスコアと評価(-r ai は 1 行、-r json、-r badge) |
--health-input FILE | 他のツールのメトリクス(カバレッジ、テスト、セキュリティ)を健全性スコアに追加します |
--pattern "glob" | ファイル選択用の Glob パターン |
--no-gitignore | .gitignore を無視します(デフォルトでは適用されます) |
--output "path" | レポートの出力先ディレクトリ |
--silent | コンソールへの出力を抑制します(ファイルレポーターと --output の使用時に便利です) |
--list | サポートされているすべての形式を一覧表示して終了します |
--no-tips | 出力内のヒントを無効にします(stdout が TTY でない場合、または CI もしくは JSCPD_NO_TIPS が設定されている場合は自動的にスキップされます) |
--config "path" | .jscpd.json 設定ファイルへのパス |
クローンの種類:完全一致、名前変更、類似
デフォルトでは、jscpd は完全一致のクローンのみを報告します。つまり、トークン列が同一です(空白、レイアウト、およびモードによってはコメントは考慮されません)。オプトインで利用できる 2 つのファミリーを使うと、検出範囲が広がります。より多くの、より長いクローンが見つかり、「クローン」の意味も変わるため、デフォルトのスキャンの後に別々のパスとして実行してください。
これらのパスは、意図的にノイズが多くなるよう設計されています。 完全一致のクローンは、ほとんどの場合、実際のコピーです。名前変更または類似のクローンは 候補 です。これらのフラグは、2 つのブロックの意味を異なるものにしていることが多い要素(名前、値、1~2 個の文)を意図的に無視します。次のようなケースでは、誤検出が想定されます。
- 似た見た目になることが想定されているボイラープレート:DTO とモデル、設定テーブル、列挙型のようなマップ、ルートやハンドラーの登録、ビルダー
- テストファイル:
describe/itブロック、フィクスチャ、セットアップコードは、意図的に同じ形を繰り返します - 生成コード、マイグレーション、シリアライザー、プロトコルバインディング
- 言語の慣用表現:構造は共通していてもロジックは異なる、2 つの
reduceループや 2 つのswitch文 - 小さなブロック:
--ignore-identifiersを使う場合、50 トークンのブロックはほとんどがプレースホルダーになるため、--min-tokensを引き上げてください
ノイズを管理しやすくするためのルール:
- 完全一致のクローンに対処した後、フラグごとの検出結果がわかるよう、一度に 1 つのファミリーを実行してください。
- まずは保守的な設定から始めてください:
--ignore-identifiersのみ(定数が既知の問題である場合に限り--ignore-literalsを追加)、--max-gap-lines 1または2、--similarity 0.85以上、識別子パスでは--min-tokens 70以上にします。絞り込んだ実行で何も見つからなかった場合にのみ、範囲を広げてください。 (renamed)または[~…]の行は、修正すべき欠陥ではなく、内容を確認するための手がかりとして扱ってください。コードベースで何が報告されるかをチームが確認していない限り、これらのパスで CI(--threshold、--fail-on-new-clones)を失敗させないでください。- 正規化した実行で見つかったものについて、どのフラグで検出されたかを明示せずに「重複が N 件見つかった」と主張しないでください。
Type-2:名前変更クローン(--ignore-identifiers、--ignore-literals、--ignore-annotations)
変数名を変更したり、定数を変えたりしたコピーです。
npx jscpd --reporters ai --ignore-identifiers <path> # function a(x) {…} matches function b(y) {…}
npx jscpd --reporters ai --ignore-identifiers --ignore-literals <path> # …and 10 matches 25, 'dev' matches 'prod'
npx jscpd --reporters ai --ignore-annotations <path> # @Override / @Deprecated no longer split a clone
Clones:
basket.js:1-9 ~ cart.js:1-9 (renamed)
limits-dev.js:1-13 ~ limits-prod.js:1-13 (renamed)
---
- キーワードの意味は維持されます(
returnがretryに一致することはありません)。そのため、構造も一致している必要があります。 --ignore-annotationsが機能するのは Java、Kotlin、Scala、Groovy、Python、Dart、Swift、JavaScript、TypeScript のみです。Ruby、Perl、T-SQL、Razor、CSS では@が別の意味を持つため、無視されません。- レポート内の位置は、引き続き元のソースを指します。
- 設定キー:
ignoreIdentifiers、ignoreLiterals、ignoreAnnotations。
Type-3:ニアミスクローン(--max-gap-lines、--similarity)
数行を編集したコピー、または同じ形に書き換えた関数です。
npx jscpd --reporters ai --max-gap-lines 2 <path> # a copy with up to 2 inserted/changed lines becomes one clone
npx jscpd --reporters ai --similarity <path> # functions whose normalized syntax trees share ≥82% of their subtrees
Clones:
save-account.js:1-12 ~ save-user.js:1-11 [~0.91 gap]
orders.ts:1-14 ~ refunds.ts:1-15 [~0.85 ast]
---
--max-gap-lines Nは、完全一致パスですでに検出されたクローンのみを結合します。そのため、新たな一致を作り出すのではなく、断片化を解消します。すべての言語で機能します。間隔に含まれるトークン数が、両側に共通するトークン数を超える場合は、類似度が0.5未満になるため、結合は行われません。--similarityはすべての関数の構文木を正規化し(ローカル名とリテラルはマーカーに置き換えられ、各名前は反対側の 1 つの名前に対応します。呼び出し先の名前と演算子はそのままです)、2 つの木に共通する部分木の割合をスコア化します。そのため、名前を変更したコピーは1.0、10 個の文を含む関数に文を 1 つ挿入した場合は約0.85となり、他のメソッドを呼び出すコピーはスコアが低くなります。JavaScript、TypeScript、Python、Java、Kotlin、Scala、C#、Go、Rust、C、C++、PHP、Ruby、Swift、Clojure、および Markdown 内のそれらのコードブロック、Vue、Svelte、Astro ファイルに適用されます。それ以外の形式では何もせずに終了します。別の関数内にネストされた関数はその一部として扱われ、テストファイルは対象外です。デフォルトの0.8から始め、手がかりを探す場合にのみ0.7まで下げてください。低いスコアを信じる前に、両方の関数を読んでください。- 両方に該当する場合は、
similarがrenamedより優先されます(結合されたクローンは、正規化しても同一ではなくなります)。 - 設定キー:
maxGapLines、similarity、minNodes。
種類が表示される場所
console:Clone found (javascript, renamed)、Clone found (javascript, similar (gap) ~0.91)、Clone found (javascript, similar (ast) ~0.75)。json:"kind": "exact" | "renamed" | "similar"。類似クローンの場合は、さらに"similarity"と"method": "gap" | "ast"が含まれます。また、ASTによるクローンの場合は、正規化された各ツリーのサイズがfirstFileとsecondFileの"nodes"として含まれます。sarif:ルールはjscpd/duplicate-code、jscpd/renamed-code、jscpd/similar-code(ギャップをまたいでマージされたクローン)、jscpd/similar-function(--similarityでペアにした関数とクラス)、およびjscpd/semantic-code(--semantic)です。Code Climateでは、同じ値がcheck_nameに使用されます。- デフォルトの実行では
exactクローンのみが報告され、これらの機能による出力への影響はありません。 - 正規化を行う実行では、完全一致の実行とは異なるクローンフィンガープリントが生成されます。設定ごとに別の
--baselineファイルを使用してください。
コードベースの概要(--summary)
--summaryを指定すると、リファクタリングの優先候補の概要が実行結果に追加されます。個々のクローンを詳しく調べる前に、どこからリファクタリングを始めるかの判断にお使いください。
# Compact clone list + compact summary, optimized for agents
npx jscpd --reporters ai --summary --no-tips <path>
# Rank by complexity instead of tokens, top 5 lists
npx jscpd --reporters ai --summary --summary-by complexity --summary-top 5 <path>
aiレポーターでは、各項目が1行で表示されます。
Summary by tokens (321 files, 129 folders):
files (tokens/lines/size/cx/dup%):
src/files.ts 2052/363/11662/80/0.0%
long-line/theme-branded.js 498/15/2.5K/10/93.3%
...
folders (files/tokens/lines/size):
src/core 8/5264/843/27136
...
読み方:
- 上位ファイルは
--summary-byの指標に基づいて順位付けされますが、各行にはすべての指標(tokens/lines/size/cx/dup%)が含まれます。 - cxは、トークンストリームから算出する言語に依存しない循環的複雑度の推定値です(1に、
if、while、&&などの判定ポイントとなるトークンを加えた値)。正確な指標ではなく、順位付けの目安としてお使いください。 - **dup%**は、検出されたクローンが占めるファイルの行の割合です。
dup%が高い大きなファイルは、リファクタリングの最適な候補です。現在の実行で検出されたクローンを反映するため、Type-2/Type-3フラグを使用すると値が上昇します。 - フォルダーでは、ファイルを直接の親ディレクトリごとに集計します(祖先ディレクトリの累積合計はありません)。
consoleレポーターでは、概要が列を揃えた表として表示されます。jsonレポートでは、追加のsummaryキーとして表示されます(フラグをオフにすると含まれません)。
設定ファイルでは、"summary": true、"summaryTop": 10、"summaryBy": "tokens"に相当します。
フォーマットをまたいだクローン検出
デフォルトでは、各フォーマットは同じフォーマットとのみ比較されます。--cross-formatsでは、関連するフォーマットを比較グループにまとめ、同じ比較対象に含めます。これにより、.jsファイルと.tsファイルの間で重複しているコードブロックがクローンとして報告されます。
# One group: compare JavaScript and TypeScript files together
npx jscpd --reporters ai --cross-formats "javascript,typescript" <path>
# Preset covering javascript, jsx, typescript, tsx
npx jscpd --reporters ai --cross-formats "js-ts" <path>
# Multiple groups are separated by ";"
npx jscpd --reporters ai --cross-formats "javascript,typescript;css,scss" <path>
注意:
- グループにTypeScriptとJavaScriptの両方を含める場合、TSファイルは消去可能な型構文を取り除いて比較されます。そのため、
function f(a: number): voidはfunction f(a)と一致します。報告される位置は元のソースを参照します。 - グループには2つ以上のフォーマットが必要です。同じフォーマットを共有するグループは、1つの比較対象に統合されます。
- フォーマット別の統計では、フォーマットをまたぐクローンはグループ内のいずれか1つのフォーマットに割り当てられます。
- 設定ファイルでは、キーは
crossFormats(またはcross-formats)です。文字列("javascript,typescript;css,scss")、文字列の配列(["javascript,typescript", "css,scss"])、または配列の配列([["javascript","typescript"],["css","scss"]])を指定できます。
2 つのコードベースを比較する(--compare)
--compare SOURCE TARGET は、--semantic 埋め込みモデルを使って 2 つのフォルダーの関数を対応付け、各側のどの関数に相手側の対応関数があるかを一覧表示します。別の言語やフレームワークへの移植の進捗、または 1 つのアプリの 2 つの実装間の機能の同等性を確認できます。重複しないパスを正確に 2 つ指定する必要があり、モデルは一度だけ必要です(--semantic-download)。クローン検出は実行しません。
npx jscpd --compare python-lib/ rust-lib/ # progress of a port and what is left
npx jscpd --compare ios/ android/ -r console-full # parity, with every pair and its similarity
npx jscpd --compare python-lib/ rust-lib/ -r json -o .jscpd-compare --silent # jscpd-compare.json for an agent
関数はまずコードに基づいて対応付けられます(それぞれが相手側にとって最も近い一致である場合)。次に、大文字と小文字およびアンダースコアを無視した名前が一致し、コードも十分に類似している場合は、名前に基づいて対応付けられます。各ペアには類似度とモデルのスケールに基づくレベル(high、medium、low)が付与されます。デフォルトのレポートでは、ペアは相手側の名前ごとに個別に一覧表示されます。jscpd はテストとコードを 2 つのブロックで測定し、テストはテストとのみ対応付けます。ここでの --min-tokens のデフォルト値は 30 です。レポーター:console、console-full、json、markdown。比較の仕組みと確認方法については compare-codebases スキルを、コードの移植方法については code-migration を参照してください。
設定ファイル
プロジェクトのルートに .jscpd.json を作成します。
{
"threshold": 0,
"reporters": ["ai"],
"ignore": ["**/node_modules/**", "**/dist/**", "**/*.min.*"],
"format": ["typescript", "javascript"],
"minLines": 5,
"minTokens": 50,
"ignoreIdentifiers": false,
"ignoreLiterals": false,
"maxGapLines": 0,
"similarity": 1,
"summary": false,
"output": "./reports/jscpd"
}
重複コードのリファクタリング
クローンを検出したら、クローンの種類ごとの戦略を使ったガイド付きワークフローで重複をなくすには、dry-refactoring スキルを使用します。
→ dry-refactoring — 重複を取り除くための段階的なリファクタリング戦略とワークフロー。次のコマンドでインストールします。
npx skills add https://github.com/kucherenko/jscpd --skill dry-refactoring
コードベース全体の健全性を改善する
より広範な「このコードベースを整理する」作業では、重複、デッドコード、最大または最も複雑なファイルの順に対応し、--health/--dashboard で優先順位を付けて最後に再測定します。これには codebase-refactoring を使用します。
npx skills add https://github.com/kucherenko/jscpd --skill codebase-refactoring