From 7a90d2240aa51939208f7aa61b9612c55e744635 Mon Sep 17 00:00:00 2001 From: luvs01 Date: Fri, 7 Aug 2026 20:14:44 +0900 Subject: [PATCH] fix(ci): reject source members as comment-only changes --- .github/scripts/pr-hygiene.cjs | 58 +++++++++++++++++++++-------- .github/scripts/pr-hygiene.test.cjs | 17 +++++++++ 2 files changed, 60 insertions(+), 15 deletions(-) diff --git a/.github/scripts/pr-hygiene.cjs b/.github/scripts/pr-hygiene.cjs index a910be674c..3f6de4c2fd 100644 --- a/.github/scripts/pr-hygiene.cjs +++ b/.github/scripts/pr-hygiene.cjs @@ -89,23 +89,51 @@ function isTestPath(path) { // clever than that reads as code and keeps the requirement. function isCommentOnlyChange(patch) { if (typeof patch !== "string") return false; - const changed = patch - .split("\n") - .filter( - (line) => - (line.startsWith("+") && !line.startsWith("+++")) || - (line.startsWith("-") && !line.startsWith("---")), - ) - .map((line) => line.slice(1).trim()); - if (changed.length === 0) return false; - return changed.every( - (line) => + let changed = 0; + let oldInBlockComment = false; + let newInBlockComment = false; + + const isComment = (text, inBlockComment) => { + const line = text.trim(); + const blockComment = inBlockComment || line.startsWith("/*"); + const blockEnd = blockComment ? line.indexOf("*/") : -1; + const comment = line === "" || line.startsWith("//") || - line.startsWith("/*") || - line.startsWith("*") || - line.startsWith("#"), - ); + (blockComment && (blockEnd === -1 || line.slice(blockEnd + 2).trim() === "")); + const nextInBlockComment = blockComment && blockEnd === -1; + return { comment, nextInBlockComment }; + }; + + for (const diffLine of patch.split("\n")) { + if (diffLine.startsWith("@@")) { + oldInBlockComment = false; + newInBlockComment = false; + continue; + } + if (diffLine.startsWith("+++") || diffLine.startsWith("---")) continue; + + const marker = diffLine[0]; + if (marker !== "+" && marker !== "-" && marker !== " ") continue; + const text = diffLine.slice(1); + if (marker !== "-") { + const result = isComment(text, newInBlockComment); + newInBlockComment = result.nextInBlockComment; + if (marker === "+") { + changed += 1; + if (!result.comment) return false; + } + } + if (marker !== "+") { + const result = isComment(text, oldInBlockComment); + oldInBlockComment = result.nextInBlockComment; + if (marker === "-") { + changed += 1; + if (!result.comment) return false; + } + } + } + return changed > 0; } function hasEmptyCatch(lines) { diff --git a/.github/scripts/pr-hygiene.test.cjs b/.github/scripts/pr-hygiene.test.cjs index 16f5f79a37..af50f504b4 100644 --- a/.github/scripts/pr-hygiene.test.cjs +++ b/.github/scripts/pr-hygiene.test.cjs @@ -108,6 +108,23 @@ describe("assessHygiene", () => { } }); + it("does not mistake private or generator members for comments", () => { + for (const patch of [ + "@@\n+ #disableAuth() { return true; }", + "@@\n+ *[Symbol.iterator]() { yield secret; }", + ]) { + const failures = assessHygiene({ files: [{ filename: "src/router.ts", patch }] }); + assert.equal(failures[0].code, "missing_regression_test", patch); + } + }); + + it("recognizes block-comment continuations only inside a block comment", () => { + assert.deepEqual(assessHygiene({ files: [{ + filename: "src/router.ts", + patch: "@@\n /**\n- * old explanation\n+ * clearer explanation\n */", + }] }), []); + }); + it("classifies renamed behavior files on both sides", () => { const failures = assessHygiene({ files: [ { filename: "docs/moved.md", previous_filename: "src/router.ts", patch: "" },