Skip to content

Commit 81c3273

Browse files
committed
test:添加测试用例
1 parent bae77e4 commit 81c3273

File tree

3 files changed

+295
-32
lines changed

3 files changed

+295
-32
lines changed

src/cvbManager.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -830,13 +830,11 @@ export function applyGlobalReplace(
830830
});
831831
}
832832

833-
try {
834833
return FuzzyMatch.applyFuzzyGlobalReplace(strContent, op.m_strOldContent, op.m_strNewContent);
835-
} catch {
834+
836835
const errorMsg = `GLOBAL-REPLACE 失败:FILE:"${op.m_strFilePath}" 中未找到OLD_CONTENT: "${op.m_strOldContent}" 可能是和原文有细微差异,或者文件路径和别的文件搞错了`;
837836
console.log(errorMsg + `\n表达式: ${regPattern}`);
838837
throw new Error(errorMsg);
839-
}
840838
}
841839

842840
// 根据前锚点、内容、后锚点构建正则表达式(dotall 模式)

src/fuzzyMatch.ts

Lines changed: 142 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -37,33 +37,141 @@ export function applyFuzzyGlobalReplace(
3737
}
3838

3939
// ================ 算法核心模块 ================
40-
function normalizeContent(original: string): NormalizedContent {
41-
const mapping: number[] = [];
42-
let normalized = "";
43-
let lastCharIsWhitespace = true;
44-
let currentPos = 0;
45-
46-
for (const char of original) {
47-
if (/\s/.test(char)) {
48-
if (!lastCharIsWhitespace) {
49-
normalized += ' ';
50-
mapping.push(currentPos);
51-
lastCharIsWhitespace = true;
40+
export function normalizeContent(original: string): { content: string; mapping: number[] } {
41+
// 第一步:去除注释
42+
const { content: noComments, mapping: mapping1 } = removeComments(original);
43+
44+
// 第二步:去除符号前后的空格
45+
const { content: noSymbolSpaces, mapping: mapping2 } = removeSymbolSpaces(noComments);
46+
47+
// 第三步:将换行符改为空格,并合并连续的空格
48+
const { content: finalContent, mapping: mapping3 } = normalizeWhitespace(noSymbolSpaces);
49+
50+
// 合并 mapping
51+
const finalMapping = mapping3.map(idx => mapping2[idx]).map(idx => mapping1[idx]);
52+
53+
return { content: finalContent, mapping: finalMapping };
54+
}
55+
56+
// 辅助函数1:去除注释,并确保 mapping 数组严格对应每个输出字符(包括换行符)
57+
export function removeComments(original: string): { content: string; mapping: number[] } {
58+
const astrLines: string[] = original.split('\n');
59+
let strContent: string = "";
60+
const arrMapping: number[] = [];
61+
let nCurrentPos: number = 0;
62+
63+
for (let i = 0; i < astrLines.length; i++) {
64+
const strLine = astrLines[i];
65+
const nCommentIndex: number = strLine.indexOf('//');
66+
const strCleanLine: string = nCommentIndex !== -1 ? strLine.slice(0, nCommentIndex) : strLine;
67+
68+
// 添加清理后的行内容,并记录映射
69+
strContent += strCleanLine;
70+
for (let nI: number = 0; nI < strCleanLine.length; nI++) {
71+
arrMapping.push(nCurrentPos + nI);
5272
}
53-
currentPos++;
73+
74+
// 只有在不是最后一行时添加换行符
75+
if (i < astrLines.length - 1) {
76+
strContent += "\n";
77+
arrMapping.push(nCurrentPos + strLine.length);
78+
nCurrentPos += strLine.length + 1; // +1 表示换行符
5479
} else {
55-
normalized += char;
56-
mapping.push(currentPos);
57-
currentPos++;
58-
lastCharIsWhitespace = false;
80+
nCurrentPos += strLine.length; // 最后一行没有换行符
5981
}
6082
}
83+
return { content: strContent, mapping: arrMapping };
84+
}
6185

62-
return { content: normalized, mapping };
86+
// 辅助函数2:去除符号前后的空格
87+
export function removeSymbolSpaces(strContentIn: string): { content: string; mapping: number[] } {
88+
// 更新正则表达式,匹配常见符号
89+
const regSymbols: RegExp = /[+\-/*()\[\]{};=,'"`!&|]/;
90+
let strNewContent: string = "";
91+
const arrMapping: number[] = [];
92+
const nLen: number = strContentIn.length;
93+
94+
for (let nI: number = 0; nI < nLen; nI++) {
95+
const strCurrentChar: string = strContentIn[nI];
96+
97+
// 使用正则表达式匹配所有空白字符(空格、制表符、换行符等)
98+
if (/\s/.test(strCurrentChar) && strCurrentChar !== '\n') {
99+
// 查找向左第一个非空白字符
100+
let nPrev: number = nI - 1;
101+
while (nPrev >= 0 && /\s/.test(strContentIn[nPrev])) {
102+
nPrev--;
103+
}
104+
// 查找向右第一个非空白字符
105+
let nNext: number = nI + 1;
106+
while (nNext < nLen && /\s/.test(strContentIn[nNext])) {
107+
nNext++;
108+
}
109+
110+
let bSkipSpace: boolean = false;
111+
// 如果前一个字符是符号,跳过当前空白字符
112+
if (nPrev >= 0 && regSymbols.test(strContentIn[nPrev])) {
113+
bSkipSpace = true;
114+
}
115+
// 如果后一个字符是符号,跳过当前空白字符
116+
if (nNext < nLen && regSymbols.test(strContentIn[nNext])) {
117+
bSkipSpace = true;
118+
}
119+
120+
if (bSkipSpace) {
121+
continue; // 跳过符号附近的空白字符
122+
}
123+
}
124+
125+
// 保留非空白字符或未跳过的空白字符
126+
strNewContent += strCurrentChar;
127+
arrMapping.push(nI);
128+
}
129+
130+
return { content: strNewContent, mapping: arrMapping };
131+
}
132+
133+
// 辅助函数3:将换行符改为空格,并合并连续的空格
134+
export function normalizeWhitespace(content: string): { content: string; mapping: number[] } {
135+
let newContent = '';
136+
const mapping: number[] = [];
137+
let inNonNewlineWhitespace = false; // 跟踪连续的非换行空白字符
138+
let inNewlineSequence = false; // 跟踪连续的换行符
139+
140+
for (let i = 0; i < content.length; i++) {
141+
const char = content[i];
142+
143+
if (char === '\n') {
144+
if (!inNewlineSequence) {
145+
// 第一个换行符,保留
146+
newContent += '\n';
147+
mapping.push(i);
148+
inNewlineSequence = true;
149+
}
150+
// 重置非换行空白字符状态
151+
inNonNewlineWhitespace = false;
152+
} else if (/\s/.test(char)) { // 非换行空白字符(如空格、制表符)
153+
if (!inNonNewlineWhitespace) {
154+
// 第一个非换行空白字符,转换为单个空格
155+
newContent += ' ';
156+
mapping.push(i);
157+
inNonNewlineWhitespace = true;
158+
}
159+
// 重置换行符状态
160+
inNewlineSequence = false;
161+
} else {
162+
// 非空白字符,直接添加
163+
newContent += char;
164+
mapping.push(i);
165+
inNonNewlineWhitespace = false;
166+
inNewlineSequence = false;
167+
}
168+
}
169+
return { content: newContent, mapping };
63170
}
64171

65-
function normalizePattern(pattern: string): string {
66-
return pattern.replace(/\s+/g, ' ').trim();
172+
export function normalizePattern(pattern: string): string {
173+
const { content } = normalizeContent(pattern);
174+
return content;
67175
}
68176

69177
function findCandidatePositions(content: string, pattern: string): number[] {
@@ -95,7 +203,9 @@ function verifyMatches(
95203
candidates: number[],
96204
mapping: number[]
97205
): MatchPosition[] {
98-
const validMatches: MatchPosition[] = [];
206+
let bestMatch: MatchPosition | null = null;
207+
let minDistance = MAX_EDIT_DISTANCE + 1; // 设为比允许的最大距离大1
208+
99209
const patternLen = pattern.length;
100210

101211
candidates.forEach(start => {
@@ -106,21 +216,26 @@ function verifyMatches(
106216

107217
const substring = content.substring(start, end);
108218
const distance = calculateEditDistance(substring, pattern, MAX_EDIT_DISTANCE);
109-
110-
if (distance <= MAX_EDIT_DISTANCE) {
111-
validMatches.push({
219+
220+
if (distance < minDistance) {
221+
minDistance = distance;
222+
bestMatch = {
112223
start: mapping[start],
113224
end: mapping[end] || mapping[mapping.length - 1]
114-
});
225+
};
115226
}
116227
});
117228

118-
return processOverlaps(validMatches);
229+
return bestMatch ? [bestMatch] : [];
119230
}
120-
121231
// ================ 工具函数 ================
122232
function splitPattern(pattern: string, count: number): string[] {
123233
const segments: string[] = [];
234+
235+
if (pattern.length / count < 3) {
236+
count = pattern.length / 3;
237+
}
238+
124239
const baseLength = Math.floor(pattern.length / count);
125240
let remaining = pattern.length % count;
126241
let pos = 0;

src/test/fuzzyMatch.test.ts

Lines changed: 152 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as assert from 'assert';
22
import * as vscode from 'vscode';
33
import { applyGlobalReplace, normalizeInput } from '../cvbManager';
4+
import { normalizeContent, removeComments, normalizeWhitespace, normalizePattern, removeSymbolSpaces} from '../fuzzyMatch';
45

56
// 定义 GlobalReplaceOperation 接口
67

@@ -34,7 +35,7 @@ function normalizeData(operation: GlobalReplaceOperation): GlobalReplaceOperatio
3435
operation.m_strNewContent = normalizeInput(operation.m_strNewContent);
3536
return operation;
3637
}
37-
38+
3839
suite('Extension Test Suite', () => {
3940
vscode.window.showInformationMessage('Start all tests.');
4041

@@ -261,4 +262,153 @@ function empty() {}
261262
applyGlobalReplace(content, normalizeData(op));
262263
}, /GLOBAL-REPLACE FILE:"test.js" OLD_CONTENT /);
263264
});
264-
});
265+
});
266+
267+
suite('Normalization Full Coverage Test Suite', () =>
268+
{
269+
// 1. 测试 removeComments:多行混合注释的情况
270+
test('removeComments - 多行代码包含注释', () =>
271+
{
272+
const strInput: string = `function test() { // 这是一个函数
273+
let nValue = 10; // 这里初始化变量
274+
// 这是一整行注释
275+
return nValue; // 返回变量
276+
} // 结束函数
277+
`;
278+
279+
const stResult = removeComments(strInput);
280+
const strContent: string = stResult.content;
281+
const arrMapping: number[] = stResult.mapping;
282+
283+
const strExpectedContent: string =
284+
"function test() { \n" +
285+
" let nValue = 10; \n" +
286+
" \n" +
287+
" return nValue; \n" +
288+
"} \n";
289+
290+
assert.strictEqual(strContent, strExpectedContent, "removeComments 多行内容不正确");
291+
assert.strictEqual(arrMapping.length, strExpectedContent.length, "removeComments 多行 mapping 长度不正确");
292+
});
293+
294+
// 2. 测试 removeSymbolSpaces:符号前后空格
295+
test('removeSymbolSpaces - 符号前后带空格', () =>
296+
{
297+
const strInput: string = `a + b
298+
( x - y )
299+
{ c * d }`;
300+
const stResult = removeSymbolSpaces(strInput);
301+
const strContent: string = stResult.content;
302+
const arrMapping: number[] = stResult.mapping;
303+
304+
const strExpectedContent: string =
305+
"a+b\n" +
306+
"(x-y)\n" +
307+
"{c*d}";
308+
309+
assert.strictEqual(strContent, strExpectedContent, "removeSymbolSpaces 符号空格去除不正确");
310+
assert.strictEqual(arrMapping.length, strExpectedContent.length, "removeSymbolSpaces mapping 长度不正确");
311+
});
312+
313+
// 3. 测试 normalizeWhitespace:空白字符处理
314+
test('normalizeWhitespace - 处理换行符、tab 和连续空格', () =>
315+
{
316+
const strInput: string = `abc def
317+
ghi\t\tjkl
318+
mno pqr`;
319+
const stResult = normalizeWhitespace(strInput);
320+
const strContent: string = stResult.content;
321+
const arrMapping: number[] = stResult.mapping;
322+
323+
const strExpectedContent: string = "abc def\nghi jkl\nmno pqr";
324+
325+
assert.strictEqual(strContent, strExpectedContent, "normalizeWhitespace 处理空白字符错误");
326+
assert.strictEqual(arrMapping.length, strExpectedContent.length, "normalizeWhitespace mapping 长度错误");
327+
});
328+
329+
// 4. 测试 removeComments 对全是注释的代码
330+
test('removeComments - 代码全是注释', () =>
331+
{
332+
const strInput: string = `// 这是注释
333+
// 这也是注释
334+
// 还有注释
335+
`;
336+
337+
const stResult = removeComments(strInput);
338+
const strContent: string = stResult.content;
339+
const arrMapping: number[] = stResult.mapping;
340+
341+
const strExpectedContent: string = "\n\n\n";
342+
343+
assert.strictEqual(strContent, strExpectedContent, "removeComments 全注释去除不正确");
344+
assert.strictEqual(arrMapping.length, strExpectedContent.length, "removeComments 全注释 mapping 错误");
345+
});
346+
347+
// 5. 测试 normalizeWhitespace 处理连续换行
348+
test('normalizeWhitespace - 处理连续换行', () =>
349+
{
350+
const strInput: string = `abc
351+
352+
353+
def`;
354+
const stResult = normalizeWhitespace(strInput);
355+
const strContent: string = stResult.content;
356+
const arrMapping: number[] = stResult.mapping;
357+
358+
const strExpectedContent: string = "abc\ndef";
359+
360+
assert.strictEqual(strContent, strExpectedContent, "normalizeWhitespace 处理连续换行错误");
361+
assert.strictEqual(arrMapping.length, strExpectedContent.length, "normalizeWhitespace mapping 长度错误");
362+
});
363+
364+
// 6. 测试 removeSymbolSpaces 处理特殊符号混合情况
365+
test('removeSymbolSpaces - 复杂符号空格情况', () =>
366+
{
367+
const strInput: string = `a + ( b * c ) / [ d - e ]`;
368+
const stResult = removeSymbolSpaces(strInput);
369+
const strContent: string = stResult.content;
370+
const arrMapping: number[] = stResult.mapping;
371+
372+
const strExpectedContent: string = "a+(b*c)/[d-e]";
373+
374+
assert.strictEqual(strContent, strExpectedContent, "removeSymbolSpaces 复杂符号空格处理错误");
375+
assert.strictEqual(arrMapping.length, strExpectedContent.length, "removeSymbolSpaces mapping 长度错误");
376+
});
377+
378+
// 7. 测试 normalizeWhitespace 处理只有空格和换行符的输入
379+
test('normalizeWhitespace - 只有空格和换行符', () =>
380+
{
381+
const strInput: string = " \n \n ";
382+
const stResult = normalizeWhitespace(strInput);
383+
const strContent: string = stResult.content;
384+
const arrMapping: number[] = stResult.mapping;
385+
386+
const strExpectedContent: string = "\n";
387+
388+
assert.strictEqual(strContent, strExpectedContent, "normalizeWhitespace 纯空格处理错误");
389+
assert.strictEqual(arrMapping.length, strExpectedContent.length, "normalizeWhitespace mapping 长度错误");
390+
});
391+
392+
// 8. 综合测试 normalizeContent
393+
test('normalizeContent - 复杂综合测试', () =>
394+
{
395+
const strInput: string = `function test() { // 这是注释
396+
let a = 5 + 6 ; // 多个空格
397+
let b = a * 2; // 还有注释
398+
return b;
399+
}`;
400+
const stResult = normalizeContent(strInput);
401+
const strContent: string = stResult.content;
402+
const arrMapping: number[] = stResult.mapping;
403+
404+
const strExpectedContent: string =
405+
"function test(){\n" +
406+
"let a=5+6;\n" +
407+
"let b=a*2;\n" +
408+
"return b;\n" +
409+
"}";
410+
411+
assert.strictEqual(strContent, strExpectedContent, "normalizeContent 复杂测试错误");
412+
assert.strictEqual(arrMapping.length, strExpectedContent.length, "normalizeContent mapping 长度错误");
413+
});
414+
});

0 commit comments

Comments
 (0)