mirror of
https://gitee.com/dromara/sa-token.git
synced 2026-05-14 04:42:09 +08:00
AI: 新增 skills/remove-redundancy-import/SKILL.md,用于检查项目中的java类无效冗余导包信息并移除
This commit is contained in:
76
.cursor/skills/remove-redundancy-import/SKILL.md
Normal file
76
.cursor/skills/remove-redundancy-import/SKILL.md
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
name: remove-redundancy-import
|
||||
description: 检查 Java 类中未被引用的冗余 import 并移除。先输出待审阅计划,用户确认后执行。适用于用户要求清理冗余导包、优化 import、或执行 remove-redundancy-import 时使用。
|
||||
---
|
||||
|
||||
# 移除冗余 import
|
||||
|
||||
检查项目中所有 Java 类的未使用 import,生成清理计划供用户审阅,确认后执行移除。
|
||||
|
||||
## 使用时机
|
||||
|
||||
- 用户要求清理冗余导包
|
||||
- 用户要求优化 Java import
|
||||
- 用户明确执行 `remove-redundancy-import` 或提及本 Skill 名称
|
||||
|
||||
## 强制流程
|
||||
|
||||
**必须先输出计划,用户确认后再执行移除。** 不得在未审阅的情况下直接修改文件。
|
||||
|
||||
## 工作流程
|
||||
|
||||
### 第一步:扫描与解析
|
||||
|
||||
**优先使用内置脚本**:在 Skill 目录下的 [scan_redundant_imports.py](scan_redundant_imports.py) 已实现完整扫描逻辑,可直接复用。
|
||||
|
||||
```bash
|
||||
# 在项目根目录执行
|
||||
python .cursor/skills/remove-redundancy-import/scan_redundant_imports.py
|
||||
# 或指定扫描根路径
|
||||
python .cursor/skills/remove-redundancy-import/scan_redundant_imports.py .
|
||||
```
|
||||
|
||||
脚本输出格式:`文件路径 | 冗余import1; import2 | 数量`,末尾两行为 `TOTAL_FILES:N` 和 `TOTAL_IMPORTS:M`。
|
||||
|
||||
**若无 Python 环境**,可手动执行:
|
||||
1. 使用 `Glob` 查找项目内所有 `**/*.java` 文件
|
||||
2. 对每个文件:提取 `package`、`import`,按 [reference.md](reference.md) 判定是否被使用
|
||||
3. 汇总存在冗余 import 的文件及列表
|
||||
|
||||
### 第二步:输出计划
|
||||
|
||||
使用下方模板生成计划报告,等待用户确认:
|
||||
|
||||
```markdown
|
||||
## 冗余 import 清理计划
|
||||
|
||||
| 文件 | 待移除 import | 数量 |
|
||||
|------|---------------|------|
|
||||
| path/to/Foo.java | `java.util.Date`, `java.sql.Timestamp` | 2 |
|
||||
| ... | ... | ... |
|
||||
|
||||
**共 N 个文件,M 处冗余 import。确认后执行移除。**
|
||||
```
|
||||
|
||||
### 第三步:执行移除
|
||||
|
||||
用户确认后,对计划中的每个文件使用 `StrReplace` 移除对应 import 行:
|
||||
|
||||
- 逐行移除,每行格式为 `import ...;` 或 `import static ...;`
|
||||
- 若某 import 后紧跟空行,可一并移除空行以保持格式整洁
|
||||
- 移除后确认文件无语法错误
|
||||
|
||||
## 检测规则概要
|
||||
|
||||
- **普通 import**:取最后一段类名(如 `java.util.List` → `List`),在类体中搜索 `\bList\b`
|
||||
- **static import**:取方法/字段名,在类体中搜索
|
||||
- **同包冗余**:import 的包与当前文件 `package` 相同则视为冗余
|
||||
- **通配符**:`import pkg.*` 跳过,不自动处理
|
||||
|
||||
详见 [reference.md](reference.md)。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 通配符 import 无法可靠判断,一律跳过
|
||||
- 注解中的类型引用采用保守策略,宁可漏检不误删
|
||||
- 移除后建议用户运行 `mvn compile` 验证
|
||||
63
.cursor/skills/remove-redundancy-import/reference.md
Normal file
63
.cursor/skills/remove-redundancy-import/reference.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# 冗余 import 检测规则
|
||||
|
||||
## 解析步骤
|
||||
|
||||
### 1. 提取 package
|
||||
|
||||
匹配 `package\s+([\w.]+)\s*;`,得到当前文件所在包。
|
||||
|
||||
### 2. 提取 import
|
||||
|
||||
匹配以下模式(每行一条):
|
||||
|
||||
- `import\s+([\w.]+)\s*;` — 普通 import
|
||||
- `import\s+static\s+([\w.]+)\s*;` — static 导入类
|
||||
- `import\s+static\s+([\w.]+)\.(\w+)\s*;` — static 导入成员(方法/字段)
|
||||
- `import\s+[\w.]+\s*\.\s*\*\s*;` — 通配符,**跳过不处理**
|
||||
|
||||
### 3. 确定简单名(Simple Name)
|
||||
|
||||
| import 类型 | 示例 | 简单名 |
|
||||
|-------------|------|--------|
|
||||
| 普通类 | `import java.util.List;` | `List` |
|
||||
| 内部类 | `import pkg.Outer.Inner;` | `Inner` |
|
||||
| static 类 | `import static pkg.Utils;` | `Utils` |
|
||||
| static 成员 | `import static pkg.Utils.foo;` | `foo` |
|
||||
|
||||
### 4. 同包冗余
|
||||
|
||||
若 `import x.y.Z` 的包 `x.y` 与当前文件 `package x.y` 相同,则该 import 冗余(同包无需导入)。
|
||||
|
||||
### 5. 使用检测
|
||||
|
||||
在**类体**(`package` 和所有 `import` 之后)中搜索:
|
||||
|
||||
- 使用正则 `\bSimpleName\b` 匹配整词,避免误匹配子串
|
||||
- 排除:注释、字符串字面量中的出现
|
||||
- 若未找到匹配,则该 import 视为未使用
|
||||
|
||||
## 边界情况
|
||||
|
||||
| 情况 | 处理方式 |
|
||||
|------|----------|
|
||||
| `import pkg.*;` | 跳过,不自动移除 |
|
||||
| 注解中的类型 `@Foo` | 若 `Foo` 为 import 的简单名,视为已使用 |
|
||||
| 泛型 `List<String>` | `List` 会匹配,视为已使用 |
|
||||
| 同名类(如 `java.util.Date` 与 `java.sql.Date`) | 两 import 都保留;若仅一个被使用,只移除未使用的 |
|
||||
| Javadoc `@param` 中的类型 | 保守:若不确定则保留 |
|
||||
|
||||
## 正则参考
|
||||
|
||||
```
|
||||
// package
|
||||
package\s+([\w.]+)\s*;
|
||||
|
||||
// 普通 import(非通配符)
|
||||
import\s+(?!static)([\w.]+)\s*;
|
||||
|
||||
// static import 成员
|
||||
import\s+static\s+[\w.]+\.(\w+)\s*;
|
||||
|
||||
// static import 类
|
||||
import\s+static\s+([\w.]+)\s*;
|
||||
```
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
冗余 import 扫描脚本
|
||||
按 reference.md 规则扫描项目内 Java 文件,输出待移除的冗余 import 列表。
|
||||
用法:在项目根目录执行 python scan_redundant_imports.py
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def get_simple_name(imp: str) -> str | None:
|
||||
"""从 import 行提取简单名(用于类体搜索)"""
|
||||
m = re.match(r'import\s+static\s+[\w.]+\.(\w+)\s*;', imp)
|
||||
if m:
|
||||
return m.group(1)
|
||||
m = re.match(r'import\s+(?:static\s+)?([\w.]+)\s*;', imp)
|
||||
if m:
|
||||
return m.group(1).split('.')[-1]
|
||||
return None
|
||||
|
||||
|
||||
def get_import_full(imp: str) -> str:
|
||||
"""提取 import 的完整限定名"""
|
||||
m = re.match(r'import\s+(?:static\s+)?([\w.]+)\s*;', imp)
|
||||
return m.group(1).strip() if m else ''
|
||||
|
||||
|
||||
def get_import_package(imp: str) -> str:
|
||||
"""提取 import 所在包(用于同包冗余判断)"""
|
||||
m = re.match(r'import\s+(?:static\s+)?([\w.]+)\s*;', imp)
|
||||
if m:
|
||||
parts = m.group(1).split('.')
|
||||
return '.'.join(parts[:-1]) if len(parts) > 1 else ''
|
||||
return ''
|
||||
|
||||
|
||||
def find_class_body_start(content: str) -> int:
|
||||
"""找到类体起始位置(最后一个 import 之后)"""
|
||||
last = 0
|
||||
for m in re.finditer(r'import\s+(?:static\s+)?[\w.]+\s*;', content):
|
||||
last = m.end()
|
||||
return last
|
||||
|
||||
|
||||
def main() -> None:
|
||||
root = sys.argv[1] if len(sys.argv) > 1 else '.'
|
||||
skip_dirs = {'target', 'build', '.git', 'node_modules'}
|
||||
|
||||
results = []
|
||||
for dirpath, dirnames, filenames in os.walk(root):
|
||||
dirnames[:] = [d for d in dirnames if d not in skip_dirs]
|
||||
for f in filenames:
|
||||
if not f.endswith('.java'):
|
||||
continue
|
||||
path = os.path.join(dirpath, f).replace('\\', '/')
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8', errors='ignore') as fp:
|
||||
content = fp.read()
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
pkg_match = re.search(r'package\s+([\w.]+)\s*;', content)
|
||||
file_pkg = pkg_match.group(1) if pkg_match else ''
|
||||
|
||||
imports = re.findall(r'import\s+(?:static\s+)?[\w.]+\s*;', content)
|
||||
imports = [i for i in imports if '*;' not in i and '.*' not in i]
|
||||
|
||||
body_start = find_class_body_start(content)
|
||||
body = content[body_start:]
|
||||
|
||||
redundant = []
|
||||
for imp in imports:
|
||||
simple = get_simple_name(imp)
|
||||
if not simple:
|
||||
continue
|
||||
imp_full = get_import_full(imp)
|
||||
imp_pkg = get_import_package(imp)
|
||||
if imp_pkg and imp_pkg == file_pkg:
|
||||
redundant.append(imp_full)
|
||||
continue
|
||||
if not re.search(r'\b' + re.escape(simple) + r'\b', body):
|
||||
redundant.append(imp_full)
|
||||
|
||||
if redundant:
|
||||
results.append((path, redundant))
|
||||
|
||||
for path, red in results:
|
||||
print(f"{path} | {'; '.join(red)} | {len(red)}")
|
||||
print("TOTAL_FILES:" + str(len(results)))
|
||||
print("TOTAL_IMPORTS:" + str(sum(len(r[1]) for r in results)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user