Files
claude-code/spec/feature_20260510_F001_multi-encoding-file-tools/spec-plan-task-1.md
claude-code-best 0ce8f7a1cb feat: 添加 GBK 编码自动检测支持,文件读写工具透明处理非 UTF-8 文件
新增 encoding.ts 核心模块实现三层编码检测(BOM → UTF-8 fatal → GBK 回退),
改造同步/异步读取路径和写入路径,使 FileReadTool/FileEditTool/FileWriteTool
能正确处理 GBK 编码文件。包含完整单元测试和 spec 文档。

Co-Authored-By: glm-5-turbo <zai-org@claude-code-best.win>
2026-05-10 20:50:12 +08:00

142 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
### Task 1: 编码检测核心模块
**背景:**
当前 `src/utils/fileRead.ts``detectEncodingForResolvedPath` 仅通过 BOM 头识别 UTF-8 和 UTF-16LE其他所有文件一律返回 `utf8`,导致 GBK 等非 UTF-8 编码文件读取乱码。本 Task 新建独立的编码检测工具模块 `src/utils/encoding.ts`实现三层编码检测算法BOM → UTF-8 fatal 验证 → GBK 回退),为后续 Task 2/3/4 的读写路径改造提供统一的编码检测和解码能力。本 Task 无前置依赖,是后续所有 Task 的基础。
**涉及文件:**
- 新建: `src/utils/encoding.ts`
- 新建: `src/utils/__tests__/encoding.test.ts`
**执行步骤:**
- [x] 创建 `src/utils/encoding.ts`,定义类型
- 位置: 文件顶部
- 导出以下类型:
```typescript
/** 扩展编码类型,覆盖最常见的非 UTF-8 CJK 编码 */
export type FileEncoding = BufferEncoding | 'gbk'
/** TextDecoder 接受的编码名string比 FileEncoding 更宽泛 */
export type DetectedEncoding = string
```
- 原因: 后续 Task 2/3/4 需要这些类型来做编码标注和类型收窄
- [x] 实现 `detectEncoding(buffer: Buffer): FileEncoding` 函数
- 位置: `src/utils/encoding.ts`,类型定义之后
- 三层检测逻辑:
```typescript
export function detectEncoding(buffer: Buffer): FileEncoding {
// Layer 1: BOM 检测(与现有 fileRead.ts 逻辑一致)
if (buffer.length >= 2 && buffer[0] === 0xff && buffer[1] === 0xfe) {
return 'utf-16le'
}
if (
buffer.length >= 3 &&
buffer[0] === 0xef &&
buffer[1] === 0xbb &&
buffer[2] === 0xbf
) {
return 'utf-8'
}
// Layer 2: UTF-8 fatal 验证
// fatal: true 模式下,无效 UTF-8 字节序列会抛出 TypeError
try {
new TextDecoder('utf-8', { fatal: true }).decode(buffer)
return 'utf-8'
} catch {
// 不是合法 UTF-8进入 Layer 3
}
// Layer 3: GBK 回退
try {
new TextDecoder('gbk', { fatal: true }).decode(buffer)
return 'gbk'
} catch {
// 不是合法 GBKlatin1 作为最终兜底
}
return 'latin1'
}
```
- 原因: BOM 必须优先于 fatal 验证GBK 作为唯一回退避免了多编码链的字节歧义问题latin1 单字节编码永远成功
- [x] 实现 `decodeBuffer(buffer: Buffer, encoding: DetectedEncoding): string` 函数
- 位置: `src/utils/encoding.ts``detectEncoding` 之后
- 逻辑:
```typescript
export function decodeBuffer(
buffer: Buffer,
encoding: DetectedEncoding,
): string {
return new TextDecoder(encoding).decode(buffer)
}
```
- 原因: 统一解码入口,后续 Task 2/3 的读取路径都调用此函数
- [x] 实现 `encodeString(content: string, encoding: DetectedEncoding): { buffer: Buffer; converted: boolean }` 函数
- 位置: `src/utils/encoding.ts``decodeBuffer` 之后
- 逻辑:
```typescript
export function encodeString(
content: string,
encoding: DetectedEncoding,
): { buffer: Buffer; converted: boolean } {
if (encoding === 'utf-8' || encoding === 'utf8') {
return { buffer: Buffer.from(content, 'utf-8'), converted: false }
}
if (encoding === 'utf-16le') {
return { buffer: Buffer.from(content, 'utf-16le'), converted: false }
}
// 其他编码(如 gbk尝试 Buffer.from失败则回退为 UTF-8
try {
const buf = Buffer.from(content, encoding as BufferEncoding)
return { buffer: buf, converted: false }
} catch {
return { buffer: Buffer.from(content, 'utf-8'), converted: true }
}
}
```
- 原因: `Buffer.from` 在 Bun 中可能支持 GBK 编码名,但 Node.js 不支持。try-catch 策略兼容两种运行时;`converted` 标志让 Task 4 的写入路径能向用户报告编码转换
- [x] 为编码检测和解码函数编写单元测试
- 测试文件: `src/utils/__tests__/encoding.test.ts`
- 测试场景:
- **BOM 检测 — UTF-16LE**: 输入 `Buffer.from([0xff, 0xfe, 0x48, 0x00])` → 预期返回 `'utf-16le'`
- **BOM 检测 — UTF-8 BOM**: 输入 `Buffer.from([0xef, 0xbb, 0xbf, 0x48, 0x65])` → 预期返回 `'utf-8'`
- **UTF-8 验证**: 输入 `Buffer.from('Hello, 世界', 'utf-8')` → 预期返回 `'utf-8'`
- **GBK 检测**: 输入 `Buffer.from([0xc4, 0xe3, 0xba, 0xc3])` → 预期返回 `'gbk'`
- **空 buffer**: 输入 `Buffer.alloc(0)` → 预期返回 `'utf-8'`
- **latin1 兜底**: 输入随机字节 `Buffer.from([0x80, 0x81, 0x82, 0x83, 0x84, 0x85])` → 预期返回 `'latin1'`
- **BOM 优先于内容分析**: 输入带 UTF-8 BOM 的数据 → 预期返回 `'utf-8'`
- **decodeBuffer — UTF-8**: 输入 UTF-8 编码的 buffer + encoding `'utf-8'` → 预期返回正确的中文字符串
- **decodeBuffer — GBK**: 输入 GBK 编码的 buffer + encoding `'gbk'` → 预期返回正确的中文字符串
- **decodeBuffer — UTF-16LE**: 输入 UTF-16LE 编码的 buffer + encoding `'utf-16le'` → 预期返回正确字符串
- **decodeBuffer — 空 buffer**: 输入空 buffer → 预期返回空字符串
- **encodeString — UTF-8**: 输入字符串 + encoding `'utf-8'` → 预期 `{ converted: false }`
- **encodeString — utf8 别名**: 输入字符串 + encoding `'utf8'` → 预期 `{ converted: false }`
- **encodeString — UTF-16LE**: 输入字符串 + encoding `'utf-16le'` → 预期 `{ converted: false }`
- **encodeString — GBK**: 输入字符串 + encoding `'gbk'` → 预期返回有效的 Bufferconverted 视运行时而定)
- 运行命令: `bun test src/utils/__tests__/encoding.test.ts`
- 预期: 所有测试通过
**检查步骤:**
- [x] 验证 `encoding.ts` 文件存在且导出正确
- `grep -c "export" src/utils/encoding.ts`
- 预期: 输出 >= 4至少导出 FileEncoding, DetectedEncoding, detectEncoding, decodeBuffer, encodeString 共 5 个导出)
- [x] 验证类型检查通过
- `bunx tsc --noEmit src/utils/encoding.ts 2>&1 | head -5`
- 预期: 无类型错误输出
- [x] 运行编码检测单元测试
- `bun test src/utils/__tests__/encoding.test.ts`
- 预期: 所有测试通过,无失败用例
**认知变更:**
- [x] [CLAUDE.md] `src/utils/encoding.ts` 是文件编码检测的唯一入口,提供 `detectEncoding`三层检测BOM → UTF-8 fatal → GBK 回退)和 `decodeBuffer`/`encodeString` 函数。检测基于文件头部 4KB零外部依赖仅使用 TextDecoder API。`FileEncoding` 类型为 `BufferEncoding | 'gbk'`,覆盖最常见非 UTF-8 CJK 编码。latin1 作为最终兜底编码(单字节编码永远成功)。
---