Skip to content

Commit 4b582fc

Browse files
committed
fix:修复TCVB某些情况下会解析失败的情况, 且添加单元测试用例
1 parent fde50f1 commit 4b582fc

File tree

7 files changed

+1764
-22
lines changed

7 files changed

+1764
-22
lines changed

package-lock.json

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,10 +436,12 @@
436436
"release": "standard-version"
437437
},
438438
"devDependencies": {
439+
"@types/async-retry": "^1.4.9",
439440
"@types/axios": "^0.14.4",
440441
"@types/cheerio": "^0.22.35",
441442
"@types/estree": "^1.0.7",
442443
"@types/mocha": "^10.0.10",
444+
"@types/needle": "^3.3.0",
443445
"@types/node": "20.x",
444446
"@types/pdf-parse": "^1.1.5",
445447
"@types/vscode": "^1.70.0",

prompt/test.txt

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,217 @@
22
做本插件的基本教学
33
快捷跳转到常用命令
44
代码放到独立文件里
5+
以下这段tcvb输出没有正确的覆盖到cvb
6+
7+
【第一步:错误原因分析】
8+
9+
1. 错误类型:操作段落结构不完整
10+
原因描述:第一个GLOBAL-REPLACE操作缺少NEW_CONTENT段落
11+
文件路径:k:\lab\CodeReDesign\src\cvbManager.ts
12+
错误写法:
13+
```
14+
## OPERATION:GLOBAL-REPLACE
15+
## OLD_CONTENT
16+
```
17+
...原内容...
18+
## OPERATION:GLOBAL-REPLACE <- 这里直接开始新操作
19+
```
20+
正确写法:
21+
```
22+
## OPERATION:GLOBAL-REPLACE
23+
## OLD_CONTENT
24+
```
25+
...原内容...
26+
```
27+
## NEW_CONTENT <- 必须添加此段落
28+
```
29+
...新内容...
30+
```
31+
32+
2. 错误类型:代码块闭合标记缺失
33+
原因描述:OLD_CONTENT代码块缺少闭合的```
34+
文件路径:k:\lab\CodeReDesign\src\cvbManager.ts
35+
错误写法:
36+
```
37+
## OLD_CONTENT
38+
```
39+
private parse(tcStrContent: string): void {
40+
// 从文件内容中提取...
41+
// 缺少闭合反引号
42+
```
43+
正确写法:
44+
```
45+
## OLD_CONTENT
46+
```
47+
private parse(...) { ... }
48+
``` <- 添加闭合标记
49+
50+
【第二步:最小改动修正】
51+
52+
文件路径:k:\lab\CodeReDesign\src\cvbManager.ts
53+
54+
修改点1:补充第一个操作的NEW_CONTENT段落
55+
位置:第一个GLOBAL-REPLACE操作块末尾
56+
修改前:
57+
```
58+
// 从文件内容中提取 "## BEGIN_TCVB" 和 "## END_TCVB" 之间的部分
59+
const regTCVB: RegExp = /##\s*BEGIN_TCVB\s*([\s\S]*?)\s*##\s*END_TCVB/;
60+
const arrTCVBMatch: RegExpExecArray | null = regTCVB.exec(tcStrContent);
61+
if (!arrTCVBMatch) {
62+
throw new Error(
63+
"文件内容必须包含 '## BEGIN_TCVB' 和 '## END_TCVB' 之间的内容,文件不完整"
64+
);
65+
}
66+
```
67+
修改后:
68+
```
69+
// 从文件内容中提取 "## BEGIN_TCVB" 和 "## END_TCVB" 之间的部分
70+
const regTCVB: RegExp = /##\s*BEGIN_TCVB\s*([\s\S]*?)\s*##\s*END_TCVB/;
71+
const arrTCVBMatch: RegExpExecArray | null = regTCVB.exec(tcStrContent);
72+
if (!arrTCVBMatch) {
73+
throw new Error(
74+
"文件内容必须包含 '## BEGIN_TCVB' 和 '## END_TCVB' 之间的内容,文件不完整"
75+
);
76+
}
77+
```
78+
## NEW_CONTENT
79+
```
80+
81+
修改点2:补全代码块闭合标记
82+
位置:第一个OLD_CONTENT代码块末尾
83+
修改前:
84+
```
85+
);
86+
}
87+
```
88+
修改后:
89+
```
90+
);
91+
}
92+
```
93+
94+
【第三步:完整输出】
95+
96+
## BEGIN_TCVB
97+
98+
## FILE:k:\lab\CodeReDesign\src\cvbManager.ts
99+
## OPERATION:GLOBAL-REPLACE
100+
## OLD_CONTENT
101+
```
102+
private parse(tcStrContent: string): void {
103+
// 从文件内容中提取 "## BEGIN_TCVB" 和 "## END_TCVB" 之间的部分
104+
const regTCVB: RegExp = /##\s*BEGIN_TCVB\s*([\s\S]*?)\s*##\s*END_TCVB/;
105+
const arrTCVBMatch: RegExpExecArray | null = regTCVB.exec(tcStrContent);
106+
if (!arrTCVBMatch) {
107+
throw new Error(
108+
"文件内容必须包含 '## BEGIN_TCVB' 和 '## END_TCVB' 之间的内容,文件不完整"
109+
);
110+
}
111+
```
112+
## NEW_CONTENT
113+
```
114+
private parse(tcStrContent: string): void {
115+
// 尝试自动修复格式问题
116+
const fixedContent = TCVB.fixTCVBFormat(tcStrContent);
117+
118+
// 从修复后的内容中提取有效部分
119+
const regTCVB: RegExp = /##\s*BEGIN_TCVB\s*([\s\S]*?)\s*##\s*END_TCVB/;
120+
const arrTCVBMatch: RegExpExecArray | null = regTCVB.exec(fixedContent);
121+
if (!arrTCVBMatch) {
122+
// 二次修复:尝试添加缺失的END标记
123+
const retryContent = fixedContent + '\n## END_TCVB';
124+
const retryMatch = regTCVB.exec(retryContent);
125+
if (!retryMatch) {
126+
throw new Error(
127+
`修复失败,TCVB格式严重错误。原始内容片段:\n${tcStrContent.substr(0, 200)}...`
128+
);
129+
}
130+
tcStrContent = retryMatch[1];
131+
} else {
132+
tcStrContent = arrTCVBMatch[1];
133+
}
134+
```
135+
136+
## OPERATION:GLOBAL-REPLACE
137+
## OLD_CONTENT
138+
```
139+
export class TCVB {
140+
private m_arrOperations: TcvbOperation[] = [];
141+
142+
constructor(tcStrContent: string) {
143+
this.parse(tcStrContent);
144+
}
145+
```
146+
## NEW_CONTENT
147+
```
148+
export class TCVB {
149+
private m_arrOperations: TcvbOperation[] = [];
150+
151+
constructor(tcStrContent: string) {
152+
try {
153+
this.parse(tcStrContent);
154+
} catch (error) {
155+
// 首次解析失败后尝试修复
156+
const fixedContent = TCVB.fixTCVBFormat(tcStrContent);
157+
this.parse(fixedContent);
158+
}
159+
}
160+
161+
// ================== 新增自动修复方法 ==================
162+
public static fixTCVBFormat(rawContent: string): string {
163+
// 第一层修复:基础结构修复
164+
let content = rawContent
165+
// 统一指令格式
166+
.replace(/##\s*([A-Z]+)/g, (m, p1) => `## ${p1.toUpperCase()}`)
167+
// 修复BEGIN/END标记
168+
.replace(/##BEGIN_TCVB/g, '## BEGIN_TCVB')
169+
.replace(/##END_TCVB/g, '## END_TCVB');
170+
171+
// 第二层修复:代码块闭合
172+
content = content.replace(
173+
/(## (?:OLD_CONTENT|NEW_CONTENT))\n([^`]+?)(?=\n## |\n## END_TCVB)/g,
174+
'$1\n```\n$2\n```'
175+
);
176+
177+
// 第三层修复:操作类型标准化
178+
const operationTypes = ['GLOBAL-REPLACE', 'CREATE', 'EXACT-REPLACE'];
179+
operationTypes.forEach(op => {
180+
const regex = new RegExp(`## OPERATION:${op.replace('-','[-_]?')}`, 'gi');
181+
content = content.replace(regex, `## OPERATION:${op}`);
182+
});
183+
184+
// 第四层修复:文件路径规范化
185+
content = content.replace(
186+
/^## FILE:\s*([^\n]+)/gm,
187+
(_, path) => `## FILE:${path.replace(/\\/g, '/').trim()}`
188+
);
189+
190+
// 第五层修复:确保END标记存在
191+
if (!/##\s*END_TCVB/.test(content)) {
192+
content += '\n## END_TCVB';
193+
}
194+
195+
return content;
196+
}
197+
```
198+
199+
## END_TCVB
200+
201+
【第四步:总结说明】
202+
203+
错误原因总结:
204+
1. 操作结构不完整:第一个GLOBAL-REPLACE操作缺少NEW_CONTENT段落
205+
2. 代码块闭合异常:OLD_CONTENT代码块缺少闭合反引号
206+
207+
修正方案验证:
208+
1. 补全操作结构:确保每个GLOBAL-REPLACE操作都包含OLD/NEW_CONTENT对
209+
2. 严格闭合代码块:所有代码块都使用```包裹
210+
3. 文件路径标准化:统一使用正斜杠路径格式
211+
4. 保留原有正确部分:未改动的代码段落保持原样
212+
213+
最终验证:
214+
1. 所有OPERATION都包含完整段落结构
215+
2. 代码块闭合标记完整
216+
3. 文件路径格式统一
217+
4. 原有正确逻辑完整保留
218+
5. 新增修复方法符合TS语法规范

src/cvbManager.ts

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ import {getOutputChannel, getCurrentOperationController} from './extension';
1010

1111
import * as FuzzyMatch from './fuzzyMatch';
1212

13+
14+
/**
15+
* 预处理输入字符串,将所有换行符(\r\n、\r、\n)统一转换为 \n
16+
* @param input 输入字符串
17+
* @returns 转换后的字符串,所有换行符替换为 \n
18+
*/
19+
function normalizeAllLineEndings(input: string): string {
20+
return input.replace(/\r\n|\r|\n/g, '\n');
21+
}
22+
1323
// ================== CVB 核心类 ==================
1424
export class Cvb {
1525
private m_recMetadata: Record<string, string>;
@@ -73,11 +83,14 @@ export class Cvb {
7383
return cvbContent;
7484
}
7585

86+
7687
private parse(strCvbContent: string): {
7788
cvbContent: string;
7889
metadata: Record<string, string>;
7990
files: Record<string, string>;
8091
} {
92+
93+
strCvbContent = normalizeAllLineEndings(strCvbContent);
8194
// 查找 CVB 开始与结束标记
8295
const regCvbStart: RegExp = /^## BEGIN_CVB$/m;
8396
const arrStartMatch = regCvbStart.exec(strCvbContent);
@@ -241,22 +254,33 @@ export class TCVB {
241254
}
242255

243256
private parse(tcStrContent: string): void {
244-
// 从文件内容中提取 "## BEGIN_TCVB" 和 "## END_TCVB" 之间的部分
245-
const regTCVB: RegExp = /##\s*BEGIN_TCVB\s*([\s\S]*?)\s*##\s*END_TCVB/;
246-
const arrTCVBMatch: RegExpExecArray | null = regTCVB.exec(tcStrContent);
247-
if (!arrTCVBMatch) {
248-
throw new Error(
249-
"文件内容必须包含 '## BEGIN_TCVB' 和 '## END_TCVB' 之间的内容,文件不完整"
250-
);
257+
258+
tcStrContent = normalizeAllLineEndings(tcStrContent);
259+
260+
// 查找 TCVB 开始标记
261+
const regTCVBStart: RegExp = /^##\s*BEGIN_TCVB$/m;
262+
const arrStartMatch = regTCVBStart.exec(tcStrContent);
263+
if (!arrStartMatch) {
264+
throw new Error("文件内容必须包含 '## BEGIN_TCVB',文件不完整");
251265
}
252-
// 重新赋值 tcStrContent 为 BEGIN_TCVB 与 END_TCVB 之间的内容
253-
tcStrContent = arrTCVBMatch[1];
266+
const iTCVBStartIndex = arrStartMatch.index + arrStartMatch[0].length;
267+
268+
// 查找 TCVB 结束标记
269+
const regTCVBEnd: RegExp = /^##\s*END_TCVB$/m;
270+
const arrEndMatch = regTCVBEnd.exec(tcStrContent);
271+
if (!arrEndMatch) {
272+
throw new Error("文件内容必须包含 '## END_TCVB',文件不完整");
273+
}
274+
const iTCVBEndIndex = arrEndMatch.index;
275+
276+
// 提取 TCVB 部分内容
277+
const strTCVBContentPart = tcStrContent.slice(iTCVBStartIndex, iTCVBEndIndex).trim();
254278

255279
// 匹配文件块,每个文件块以 "## FILE:" 开头
256280
const regFileBlock: RegExp =
257281
/^## FILE:(.*?)\n([\s\S]*?)(?=^## FILE:|(?![\s\S]))/gm;
258282
let arrFileMatch: RegExpExecArray | null;
259-
while ((arrFileMatch = regFileBlock.exec(tcStrContent)) !== null) {
283+
while ((arrFileMatch = regFileBlock.exec(strTCVBContentPart)) !== null) {
260284
const strFilePath: string = filePathNormalize(arrFileMatch[1]);
261285
const strOperationsBlock: string = arrFileMatch[2];
262286
// 支持操作类型中含有 "-" 符号(如 exact-replace 等)

0 commit comments

Comments
 (0)