- name
- refactoring-detection
- description
- リファクタリング候補検出のast-grepパターン、推定工数算出、言語別設定を定義
# リファクタリング検出スキル
`/find-refactoring`コマンドで使用する検出ロジック・パターンを定義。
---
## 前提ツール
| ツール | 必須/任意 | 用途 | インストール |
|--------|---------|------|-------------|
| `ast-grep (sg)` | **必須** | AST解析 | `cargo install ast-grep` / `brew install ast-grep` |
| `jscpd` | 任意 | 重複検出 | `npm install -g jscpd` |
| `jq` | **必須** | JSON処理 | `brew install jq` |
| `wc`, `grep` | **必須** | 基本検出 | 標準コマンド |
### ツール確認
```bash
check_required_tools() {
local missing=()
command -v sg &>/dev/null || missing+=("ast-grep (sg): cargo install ast-grep")
command -v jq &>/dev/null || missing+=("jq: brew install jq")
if [[ ${#missing[@]} -gt 0 ]]; then
echo "Error: Missing required tools:"
printf ' - %s\n' "${missing[@]}"
return 1
fi
return 0
}
check_optional_tools() {
if command -v jscpd &>/dev/null || command -v npx &>/dev/null; then
echo "jscpd: available"
else
echo "jscpd: not available (duplicate detection skipped)"
fi
}
```
---
## 言語検出
```bash
detect_language() {
if [[ -f "package.json" ]]; then
echo "typescript"
elif [[ -f "Cargo.toml" ]]; then
echo "rust"
elif [[ -f "go.mod" ]]; then
echo "go"
elif [[ -f "pyproject.toml" ]] || [[ -f "requirements.txt" ]]; then
echo "python"
else
echo "typescript"
fi
}
```
### 言語別設定
| 言語 | 拡張子 | ast-grep lang | ファイル上限 | 関数上限 |
|------|--------|---------------|-------------|---------|
| TypeScript | `*.ts,*.tsx` | `typescript`/`tsx` | 500行 | 80行 |
| Rust | `*.rs` | `rust` | 500行 | 80行 |
| Go | `*.go` | `go` | 400行 | 60行 |
| Python | `*.py` | `python` | 400行 | 60行 |
---
## 検出パターン(TypeScript)
> **注意**: ast-grep出力は0-indexed。表示時に+1する。
### サイズ系
```bash
# 500行超過ファイル
find_large_files() {
local target="$1"
find "$target" -type f \( -name "*.ts" -o -name "*.tsx" \) \
! -path "*/node_modules/*" ! -path "*/.git/*" \
-exec wc -l {} + 2>/dev/null | \
awk '$1 > 500 && !/total$/ {printf "%s:%d:file_split\n", $2, $1}'
}
# 80行超過関数(function宣言)
find_large_functions_ts() {
local target="$1"
sg run --pattern 'function $NAME($$$) { $$$ }' --lang typescript "$target" --json 2>/dev/null | \
jq -r '.[] | select(.range.end.line - .range.start.line > 80) |
"\(.file):\(.range.start.line + 1):\(.range.end.line - .range.start.line):func_split:\(.metaVariables.single.NAME.text // "anonymous")"'
}
# 80行超過アロー関数
find_large_arrow_functions() {
local target="$1"
sg run --pattern 'const $NAME = ($$$) => { $$$ }' --lang typescript "$target" --json 2>/dev/null | \
jq -r '.[] | select(.range.end.line - .range.start.line > 80) |
"\(.file):\(.range.start.line + 1):\(.range.end.line - .range.start.line):func_split:\(.metaVariables.single.NAME.text)"'
}
# 6引数超関数
find_many_params() {
local target="$1"
sg run --pattern 'function $NAME($A, $B, $C, $D, $E, $F, $G, $$$) { $$$ }' \
--lang typescript "$target" --json 2>/dev/null | \
jq -r '.[] | "\(.file):\(.range.start.line + 1):7:many_params"'
}
```
### 型安全系
```bash
# any型使用(grep - ast-grepでは型注釈のマッチが困難)
find_any_types() {
local target="$1"
grep -rn --include="*.ts" --include="*.tsx" -E ':\s*any\b' "$target" 2>/dev/null | \
grep -v node_modules | \
while IFS=: read -r file line _; do
echo "$file:$line:1:any_type"
done
}
# `ts-ignore` skill / `ts-expect-error` skill
find_ts_ignore() {
local target="$1"
grep -rn --include="*.ts" --include="*.tsx" -E '`ts-ignore` skill|`ts-expect-error` skill' "$target" 2>/dev/null | \
grep -v node_modules | \
while IFS=: read -r file line _; do
echo "$file:$line:1:ts_ignore"
done
}
```
### エラー処理系
```bash
# 空catchブロック(try-catch全体でマッチ)
find_empty_catch() {
local target="$1"
sg run --pattern 'try { $$$ } catch ($E) { }' --lang typescript "$target" --json 2>/dev/null | \
jq -r '.[] | "\(.file):\(.range.start.line + 1):1:empty_catch"'
}
```
### 品質系
```bash
# console.log残存(テスト・設定ファイル除外)
find_console_log() {
local target="$1"
sg run --pattern 'console.log($$$)' --lang typescript "$target" --json 2>/dev/null | \
jq -r '.[] | select(
(.file | test("\\.(spec|test)\\.(ts|tsx)$") | not) and
(.file | test("(jest|vitest|eslint|prettier)\\.config") | not)
) | "\(.file):\(.range.start.line + 1):1:console_log"'
}
```
---
## 検出パターン(Rust)
```bash
# 80行超過関数
find_large_functions_rust() {
local target="$1"
sg run --pattern 'fn $NAME($$$) { $$$ }' --lang rust "$target" --json 2>/dev/null | \
jq -r '.[] | select(.range.end.line - .range.start.line > 80) |
"\(.file):\(.range.start.line + 1):\(.range.end.line - .range.start.line):func_split:\(.metaVariables.single.NAME.text)"'
}
# unwrap()多用(1ファイル5箇所超)
find_unwrap_abuse() {
local target="$1"
sg run --pattern '$_.unwrap()' --lang rust "$target" --json 2>/dev/null | \
jq -r 'group_by(.file) | .[] | select(length > 5) |
"\(.[0].file):1:\(length):unwrap_abuse"'
}
# todo!/unimplemented!残存
find_rust_todo() {
local target="$1"
grep -rn --include="*.rs" -E 'todo!|unimplemented!' "$target" 2>/dev/null | \
while IFS=: read -r file line _; do
echo "$file:$line:1:rust_todo"
done
}
```
---
## 検出パターン(Go)
```bash
# 60行超過関数
find_large_functions_go() {
local target="$1"
sg run --pattern 'func $NAME($$$) { $$$ }' --lang go "$target" --json 2>/dev/null | \
jq -r '.[] | select(.range.end.line - .range.start.line > 60) |
"\(.file):\(.range.start.line + 1):\(.range.end.line - .range.start.line):func_split:\(.metaVariables.single.NAME.text)"'
}
# panic残存
find_go_panic() {
local target="$1"
grep -rn --include="*.go" 'panic(' "$target" 2>/dev/null | \
grep -v '_test.go' | \
while IFS=: read -r file line _; do
echo "$file:$line:1:go_panic"
done
}
```
---
## 検出パターン(Python)
```bash
# 60行超過関数
find_large_functions_python() {
local target="$1"
sg run --pattern 'def $NAME($$$): $$$' --lang python "$target" --json 2>/dev/null | \
jq -r '.[] | select(.range.end.line - .range.start.line > 60) |
"\(.file):\(.range.start.line + 1):\(.range.end.line - .range.start.line):func_split:\(.metaVariables.single.NAME.text)"'
}
# print残存(テストファイル除外)
find_python_print() {
local target="$1"
grep -rn --include="*.py" 'print(' "$target" 2>/dev/null | \
grep -v test_ | grep -v _test.py | \
while IFS=: read -r file line _; do
echo "$file:$line:1:python_print"
done
}
```
---
## 重複コード検出
```bash
find_duplicates() {
local target="$1"
local cmd=""
if command -v jscpd &>/dev/null; then
cmd="jscpd"
elif command -v npx &>/dev/null; then
cmd="npx jscpd"
else
return 0 # スキップ
fi
$cmd "$target" --min-lines 10 --reporters json --silent 2>/dev/null | \
jq -r '.duplicates[]? |
"\(.firstFile.name):\(.firstFile.start):\(.firstFile.end - .firstFile.start):duplicate:\(.secondFile.name)"' 2>/dev/null || true
}
```
---
Ver en GitHub