From c66bced0fac7c463082eb3a371687b57185c3d8b Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Sun, 7 Jun 2026 13:37:31 +0800 Subject: [PATCH] fix: limit \r\n escape preprocessing to anchor/find params only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit normalizeContentLineEndings 在 8e637db7 引入的 \r\n escape 预处理对 所有调用路径生效,导致 workspaceWrite 写入 C 源码时破坏 \r\n 字符串常量。 修复:增加 unescapeEscapeSequences 参数(默认 false),仅在 workspaceReplace find、workspaceInsertAfter anchor、diagnosticDetails 三个 anchor 匹配路径启用 escape 预处理。 - workspaceWrite content: 不启用(保留原始 \r\n) - workspaceReplace replace text: 不启用 - workspaceInsertAfter insertText: 不启用 - applyUpdateHunks output: 不启用(已是规范化内容) Fixes #1027 --- tools/src/hwpod-node-lib.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/src/hwpod-node-lib.ts b/tools/src/hwpod-node-lib.ts index 2802550b..683b8f93 100644 --- a/tools/src/hwpod-node-lib.ts +++ b/tools/src/hwpod-node-lib.ts @@ -419,7 +419,7 @@ async function workspaceReplace(args: any) { assertExpectedSha(before, args.expectedSha, relativePath); const find = requiredRawString(args.find, "find"); const replace = rawString(args.replace ?? args.replacement); - const normalizedFind = normalizeContentLineEndings(find, "\n", false); + const normalizedFind = normalizeContentLineEndings(find, "\n", false, true); const normalizedReplace = normalizeContentLineEndings(replace, "\n", false); const normalizedContent = before.normalizedContent; const occurrences = countOccurrences(normalizedContent, normalizedFind); @@ -443,7 +443,7 @@ async function workspaceInsertAfter(args: any) { assertExpectedSha(before, args.expectedSha, relativePath); const anchor = requiredRawString(args.anchor, "anchor"); const insertText = requiredRawString(args.line ?? args.insert ?? args.text, "line"); - const normalizedAnchor = normalizeContentLineEndings(anchor, "\n", false); + const normalizedAnchor = normalizeContentLineEndings(anchor, "\n", false, true); const normalizedInsert = normalizeContentLineEndings(insertText, "\n", false); const lines = splitNormalizedLines(before.normalizedContent, before.hadFinalNewline); const anchorLines = splitNormalizedLines(normalizedAnchor, false); @@ -611,7 +611,7 @@ function splitNormalizedLines(content: string, hadFinalNewline: boolean) { } function diagnosticDetails(fileState: any, expected: string, relativePath: string) { - const expectedLines = splitNormalizedLines(normalizeContentLineEndings(expected, "\n", false), false).filter((line) => line.length > 0); + const expectedLines = splitNormalizedLines(normalizeContentLineEndings(expected, "\n", false, true), false).filter((line) => line.length > 0); const normalizedLines = splitNormalizedLines(fileState.normalizedContent, fileState.hadFinalNewline); const candidates = expectedLines.slice(0, 4).flatMap((line) => candidateLines(normalizedLines, line)).slice(0, 12); return { @@ -700,9 +700,9 @@ function lineEndingFromArgs(args: any, fallback: string) { throw cliError("invalid_line_ending", "lineEnding must be preserve, lf, or crlf", { lineEnding: value }); } -function normalizeContentLineEndings(content: string, lineEnding: string, finalNewline: boolean) { - const unescaped = String(content).replace(/\\r\\n/gu, "\r\n").replace(/\\n/gu, "\n").replace(/\\r(?!\n)/gu, "\r"); - const normalized = unescaped.replace(/\r\n?/gu, "\n"); +function normalizeContentLineEndings(content: string, lineEnding: string, finalNewline: boolean, unescapeEscapeSequences = false) { + const base = unescapeEscapeSequences ? String(content).replace(/\\r\\n/gu, "\r\n").replace(/\\n/gu, "\n").replace(/\\r(?!\n)/gu, "\r") : String(content); + const normalized = base.replace(/\r\n?/gu, "\n"); const withFinal = finalNewline && !normalized.endsWith("\n") ? `${normalized}\n` : normalized; return lineEnding === "\r\n" ? withFinal.replace(/\n/gu, "\r\n") : withFinal; }