refactor(web): hasApplicableFix 改类型谓词,消除 applyConflictFix 强转
hasApplicableFix 重写为对象入参类型谓词 patch is ApplicablePatch,谓词命中后 applyConflictFix 里 original/replacement 自动收窄为 string,删两处 as string;补收窄单测。
This commit is contained in:
@@ -4,12 +4,26 @@ import { applyConflictFix, hasApplicableFix } from "./applyFix";
|
|||||||
|
|
||||||
describe("hasApplicableFix", () => {
|
describe("hasApplicableFix", () => {
|
||||||
it("requires non-empty original and string replacement", () => {
|
it("requires non-empty original and string replacement", () => {
|
||||||
expect(hasApplicableFix("迈巴赫", "奔驰")).toBe(true);
|
expect(hasApplicableFix({ original: "迈巴赫", replacement: "奔驰" })).toBe(true);
|
||||||
expect(hasApplicableFix("删我", "")).toBe(true); // 空串=删除,仍可应用
|
// 空串=删除,仍可应用
|
||||||
expect(hasApplicableFix(null, "奔驰")).toBe(false);
|
expect(hasApplicableFix({ original: "删我", replacement: "" })).toBe(true);
|
||||||
expect(hasApplicableFix("", "奔驰")).toBe(false);
|
expect(hasApplicableFix({ original: null, replacement: "奔驰" })).toBe(false);
|
||||||
expect(hasApplicableFix("迈巴赫", null)).toBe(false);
|
expect(hasApplicableFix({ original: "", replacement: "奔驰" })).toBe(false);
|
||||||
expect(hasApplicableFix(undefined, undefined)).toBe(false);
|
expect(hasApplicableFix({ original: "迈巴赫", replacement: null })).toBe(false);
|
||||||
|
expect(hasApplicableFix({ original: undefined, replacement: undefined })).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("narrows original/replacement to string when true (type predicate)", () => {
|
||||||
|
const patch: { original: string | null; replacement: string | null } = {
|
||||||
|
original: "迈巴赫",
|
||||||
|
replacement: "奔驰",
|
||||||
|
};
|
||||||
|
if (!hasApplicableFix(patch)) throw new Error("expected applicable");
|
||||||
|
// 编译期收窄:无需强转即可当 string 拼接(`as string` 已消除)。
|
||||||
|
const combined: string = patch.original + patch.replacement;
|
||||||
|
expect(combined).toBe("迈巴赫奔驰");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,25 @@ export type FixOutcome =
|
|||||||
| { status: "not-found"; text: string } // 有补丁但终稿里找不到原文(作者已改稿/漂移):text 原样
|
| { status: "not-found"; text: string } // 有补丁但终稿里找不到原文(作者已改稿/漂移):text 原样
|
||||||
| { status: "no-patch"; text: string }; // 该冲突无可自动应用的补丁:text 原样
|
| { status: "no-patch"; text: string }; // 该冲突无可自动应用的补丁:text 原样
|
||||||
|
|
||||||
|
// 已确认可应用的补丁(original/replacement 均为 string)。
|
||||||
|
export interface ApplicablePatch {
|
||||||
|
original: string;
|
||||||
|
replacement: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 松散补丁入参:original/replacement 可能缺失/为 null(审稿可局部修复时才带上)。
|
||||||
|
interface LoosePatch {
|
||||||
|
original: string | null | undefined;
|
||||||
|
replacement: string | null | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
// 是否带可应用补丁:original 非空 + replacement 为字符串(容许空串=删除)。
|
// 是否带可应用补丁:original 非空 + replacement 为字符串(容许空串=删除)。
|
||||||
export function hasApplicableFix(
|
// 类型谓词:命中即把 original/replacement 收窄为 string,调用方无需强转。
|
||||||
original: string | null | undefined,
|
export function hasApplicableFix(patch: LoosePatch): patch is ApplicablePatch {
|
||||||
replacement: string | null | undefined,
|
|
||||||
): boolean {
|
|
||||||
return (
|
return (
|
||||||
typeof original === "string" &&
|
typeof patch.original === "string" &&
|
||||||
original.length > 0 &&
|
patch.original.length > 0 &&
|
||||||
typeof replacement === "string"
|
typeof patch.replacement === "string"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,18 +35,20 @@ export function applyConflictFix(
|
|||||||
original: string | null | undefined,
|
original: string | null | undefined,
|
||||||
replacement: string | null | undefined,
|
replacement: string | null | undefined,
|
||||||
): FixOutcome {
|
): FixOutcome {
|
||||||
if (!hasApplicableFix(original, replacement)) {
|
const patch: LoosePatch = { original, replacement };
|
||||||
|
if (!hasApplicableFix(patch)) {
|
||||||
return { status: "no-patch", text };
|
return { status: "no-patch", text };
|
||||||
}
|
}
|
||||||
// hasApplicableFix 已收窄为 string。
|
// 谓词已把 patch 收窄为 ApplicablePatch,original/replacement 皆为 string。
|
||||||
const from = original as string;
|
const idx = text.indexOf(patch.original);
|
||||||
const to = replacement as string;
|
|
||||||
const idx = text.indexOf(from);
|
|
||||||
if (idx === -1) {
|
if (idx === -1) {
|
||||||
return { status: "not-found", text };
|
return { status: "not-found", text };
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
status: "applied",
|
status: "applied",
|
||||||
text: text.slice(0, idx) + to + text.slice(idx + from.length),
|
text:
|
||||||
|
text.slice(0, idx) +
|
||||||
|
patch.replacement +
|
||||||
|
text.slice(idx + patch.original.length),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user