mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-25 21:20:00 +00:00
Preserve visible issue fields after pasted HTML code
Ignore HTML comment delimiters inside the fenced and indented code already excluded from issue declarations. An unfinished copied response must not erase the actual failing-version, feedback or additional-topic fields that follow it. Keep genuine hidden comments hidden, preserve community labels and verify the existing GitHub label consumer without posting or closing reports. Contract-Neutral: Restore existing visible-declaration intake semantics only; no Pulse runtime, public API, workflow identity, contact, disposition or release-policy delta. Change-source: pulse-maintainer
This commit is contained in:
@@ -59,7 +59,47 @@ function stripHTMLComments(value) {
|
||||
let previous;
|
||||
do {
|
||||
previous = stripped;
|
||||
stripped = stripped.replace(/<!--[\s\S]*?(?:-->|$)/g, "");
|
||||
let fence = null;
|
||||
let inComment = false;
|
||||
// Strip hidden template text, not literal markers in pasted code. In
|
||||
// particular, an unfinished HTML response must not swallow the real form
|
||||
// fields after its closing Markdown fence. Match the same fences and
|
||||
// indentation that declarationLines excludes from metadata declarations.
|
||||
stripped = stripped.split(/(\r?\n)/).map((text, index) => {
|
||||
if (index % 2) return text;
|
||||
const marker = text.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
|
||||
if (!inComment) {
|
||||
if (fence) {
|
||||
if (marker && marker[1][0] === fence.character &&
|
||||
marker[1].length >= fence.length && !marker[2].trim()) {
|
||||
fence = null;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
if (marker && (marker[1][0] === "~" || !marker[2].includes("`"))) {
|
||||
fence = { character: marker[1][0], length: marker[1].length };
|
||||
return text;
|
||||
}
|
||||
if (/^(?: {4}|\t)/.test(text)) return text;
|
||||
}
|
||||
let visible = "";
|
||||
let offset = 0;
|
||||
while (offset < text.length) {
|
||||
if (inComment) {
|
||||
const end = text.indexOf("-->", offset);
|
||||
if (end === -1) break;
|
||||
offset = end + 3;
|
||||
inComment = false;
|
||||
} else {
|
||||
const start = text.indexOf("<!--", offset);
|
||||
if (start === -1) return visible + text.slice(offset);
|
||||
visible += text.slice(offset, start);
|
||||
offset = start + 4;
|
||||
inComment = true;
|
||||
}
|
||||
}
|
||||
return visible;
|
||||
}).join("");
|
||||
} while (stripped !== previous);
|
||||
return stripped;
|
||||
}
|
||||
|
||||
@@ -365,6 +365,73 @@ test("hidden template fields cannot declare topics or override feedback type", (
|
||||
), "documentation");
|
||||
});
|
||||
|
||||
test("literal HTML comments in pasted code cannot erase later visible form fields", () => {
|
||||
const { extractPulseVersion, classifyAdditionalActionableTopics, classifyV6FeedbackType } = triage.internals;
|
||||
for (const newline of ["\n", "\r\n"]) {
|
||||
for (const [open, close] of [["```html", "```"], ["~~~~html", "~~~~"],
|
||||
[" ````text", " ````"]]) {
|
||||
for (const marker of ["<!-- unfinished response", "<!-- closed response -->", "<!<!-- -->-->"]) {
|
||||
const body = [open, marker, "### Pulse version", "6.4.1", close, "",
|
||||
"### Pulse version", "6.5.0", "### Feedback type", "Documentation issue",
|
||||
"### Additional actionable topics", "Missing disk reading"].join(newline);
|
||||
assert.equal(extractPulseVersion("Upgrade from v6.4.5", body), "6.5.0", body);
|
||||
assert.equal(classifyV6FeedbackType(body), "documentation", body);
|
||||
assert.equal(classifyAdditionalActionableTopics(body), true, body);
|
||||
}
|
||||
}
|
||||
for (const indent of [" ", "\t"]) {
|
||||
const body = [`${indent}<!-- copied log delimiter`, `${indent}Pulse version: 6.4.1`, "",
|
||||
"### Pulse version", "6.5.0", "### Feedback type", "Documentation issue",
|
||||
"### Additional actionable topics", "Missing disk reading"].join(newline);
|
||||
assert.equal(extractPulseVersion("Upgrade from v6.4.5", body), "6.5.0", body);
|
||||
assert.equal(classifyV6FeedbackType(body), "documentation", body);
|
||||
assert.equal(classifyAdditionalActionableTopics(body), true, body);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("hidden comments still hide their contents even when they contain code fences", () => {
|
||||
const { extractPulseVersion, classifyAdditionalActionableTopics, classifyV6FeedbackType } = triage.internals;
|
||||
const hidden = ["<!-- hidden template", "```markdown", "### Pulse version", "6.4.1",
|
||||
"### Feedback type", "Bug / regression", "### Additional actionable topics", "Copied topic", "```"];
|
||||
const visible = ["### Pulse version", "6.5.0", "### Feedback type", "Documentation issue",
|
||||
"### Additional actionable topics", "None"];
|
||||
for (const newline of ["\n", "\r\n"]) {
|
||||
const closed = [...hidden, " -->", ...visible].join(newline);
|
||||
assert.equal(extractPulseVersion("Upgrade from v6.4.5", closed), "6.5.0");
|
||||
assert.equal(classifyV6FeedbackType(closed), "documentation");
|
||||
assert.equal(classifyAdditionalActionableTopics(closed), false);
|
||||
const unfinished = [...hidden, ...visible].join(newline);
|
||||
assert.equal(extractPulseVersion("Bug", unfinished), null);
|
||||
assert.equal(classifyV6FeedbackType(unfinished), null);
|
||||
assert.equal(classifyAdditionalActionableTopics(unfinished), null);
|
||||
}
|
||||
});
|
||||
|
||||
test("pasted HTML keeps genuine topic values without turning copied headings into fields", () => {
|
||||
const body = ["### Additional actionable topics", "```html", "<!-- unmatched response marker",
|
||||
"### Pulse version", "6.4.1", "```", "### Pulse version", "6.5.0"].join("\n");
|
||||
assert.equal(triage.internals.classifyAdditionalActionableTopics(body), true);
|
||||
assert.equal(triage.internals.extractPulseVersion("Upgrade from v6.4.5", body), "6.5.0");
|
||||
});
|
||||
|
||||
test("syncLabels consumes real fields after pasted HTML without contact or state changes", async () => {
|
||||
const { github, calls } = createGithub();
|
||||
const issue = { number: 2801, title: "Upgrade from v6.4.5",
|
||||
body: ["```html", "<!-- incomplete response", "### Pulse version", "6.4.1", "```",
|
||||
"### Pulse version", "6.5.0", "### Feedback type", "Bug / regression",
|
||||
"### Additional actionable topics", "Missing disk reading"].join("\n"),
|
||||
labels: ["needs-human", "needs-retest-on-latest", "operator-reviewed", "needs-version-info"]
|
||||
.map(name => ({ name })) };
|
||||
await syncLabels({ github, core: createCore(), context: createContext({ issue }) });
|
||||
assert.deepEqual(calls.addLabels.flatMap(call => call.labels), ["affects-6.5.0", "bug", "needs-decomposition"]);
|
||||
assert.deepEqual(calls.removeLabel.map(call => call.name), ["needs-version-info"]);
|
||||
assert.deepEqual(calls.createComment, []);
|
||||
assert.deepEqual(calls.getLatestRelease, []);
|
||||
assert.deepEqual(calls.paginate, []);
|
||||
assert.deepEqual(calls.getIssue.map(call => call.issue_number), [2801]);
|
||||
});
|
||||
|
||||
test("a hidden hint or empty topic field is not an actionable declaration", () => {
|
||||
for (const value of ["", "<!-- A second bug. -->", "None\n\n<!-- Another topic. -->"]) {
|
||||
assert.equal(triage.internals.classifyAdditionalActionableTopics(
|
||||
|
||||
Reference in New Issue
Block a user