From 302e9946087e1bbd6251c94e19fe3286c01d8982 Mon Sep 17 00:00:00 2001 From: joaovictor91123 Date: Sun, 26 Jul 2026 06:42:48 -0700 Subject: [PATCH] fix(content-lane): harden duplicates block-scalar header detection Mirror source-evidence.ts block-scalar grammar so chomping/indent permutations and trailing inline comments are recognized in parseSimpleFrontmatter and findDuplicateFrontmatterKeys. Closes #9290 --- src/review/content-lane/duplicates.ts | 17 ++++++++- test/unit/content-lane-duplicates.test.ts | 46 +++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/review/content-lane/duplicates.ts b/src/review/content-lane/duplicates.ts index 64fb1cdcbd..b23100aa07 100644 --- a/src/review/content-lane/duplicates.ts +++ b/src/review/content-lane/duplicates.ts @@ -40,6 +40,10 @@ export type ContentDuplicateReview = { relatedCandidates: ContentDuplicateMatch[]; }; +function stripYamlComment(value: string): string { + return value.replace(/\s+#.*$/, "").trim(); +} + function unquoteYamlScalar(value: string): string { const trimmed = value.trim(); if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) { @@ -48,6 +52,15 @@ function unquoteYamlScalar(value: string): string { return trimmed.replace(/\s+#.*$/, "").trim(); } +// A YAML block-scalar header indicator: `|` or `>` with an optional chomping (`+`/`-`) and/or a single indentation +// digit, in EITHER order — `|`, `>`, `|-`, `>+`, `|2`, `|2-`, `|-2`, `>2+`. Kept in one place so parseSimpleFrontmatter +// and findDuplicateFrontmatterKeys agree on what is a block-scalar header (mirrors source-evidence.ts). +const BLOCK_SCALAR_INDICATOR = /^[|>](?:[+-]?\d?|\d[+-]?)$/; + +function isBlockScalarHeader(raw: string): boolean { + return BLOCK_SCALAR_INDICATOR.test(stripYamlComment(raw)); +} + /** * Parse YAML frontmatter into a flat key→string map, capturing each top-level field's value * REGARDLESS of scalar style — inline, quoted, block literal (`|`), folded (`>`), or a block/flow @@ -74,7 +87,7 @@ export function parseSimpleFrontmatter(source: string): Record { /* v8 ignore next -- noUncheckedIndexedAccess fallback: capture group 2 (.*) always participates when head matches, so head[2] is never undefined */ const inline = (head[2] ?? "").trim(); i += 1; - if (/^[|>][+-]?\d*$/.test(inline)) { + if (isBlockScalarHeader(inline)) { // Block literal (`|`) / folded (`>`) scalar: gather the indented block that follows. const block: string[] = []; /* v8 ignore next -- noUncheckedIndexedAccess fallback: i < lines.length guards the index; split() elements are always strings */ @@ -129,7 +142,7 @@ export function findDuplicateFrontmatterKeys(source: string): string[] { /* v8 ignore next -- noUncheckedIndexedAccess fallback: capture group 2 (.*) always participates when head matches, so head[2] is never undefined */ const inline = (head[2] ?? "").trim(); i += 1; - if (/^[|>][+-]?\d*$/.test(inline)) { + if (isBlockScalarHeader(inline)) { /* v8 ignore next -- noUncheckedIndexedAccess fallback: i < lines.length guards the index; split() elements are always strings */ while (i < lines.length && ((lines[i] ?? "").trim() === "" || /^\s/.test(lines[i] ?? ""))) i += 1; } else if (inline === "") { diff --git a/test/unit/content-lane-duplicates.test.ts b/test/unit/content-lane-duplicates.test.ts index 9d63f1846b..0069b9736f 100644 --- a/test/unit/content-lane-duplicates.test.ts +++ b/test/unit/content-lane-duplicates.test.ts @@ -58,6 +58,22 @@ describe("parseSimpleFrontmatter", () => { expect(f.title).toBe("Real"); expect(Object.keys(f)).toEqual(["title"]); }); + + it("recognizes block-scalar headers with chomping/indent in either order and trailing comments (#9290)", () => { + const indicators = ["|", ">", "|-", ">-", "|+", ">+", "|2", ">2", "|-2", "|2-", ">2+", ">2-"]; + for (const indicator of indicators) { + const src = ["---", `description: ${indicator}`, " line one", " line two", "title: Real", "---", "", "body"].join("\n"); + const f = parseSimpleFrontmatter(src); + expect(f.description).toBe("line one\nline two"); + expect(f.title).toBe("Real"); + } + for (const indicator of ["|-", ">", "|2-"]) { + const src = ["---", `description: ${indicator} # sources below`, " only line", "title: Real", "---", "", "body"].join("\n"); + const f = parseSimpleFrontmatter(src); + expect(f.description).toBe("only line"); + expect(f.title).toBe("Real"); + } + }); }); describe("findDuplicateFrontmatterKeys", () => { @@ -283,6 +299,36 @@ describe("findDuplicateFrontmatterKeys — block-scalar + sequence skipping", () // The blank + comment lines fail the key regex → the `if (!head) continue` branch runs; no dupes. expect(findDuplicateFrontmatterKeys(src)).toEqual([]); }); + + it("skips block-scalar headers with chomping/indent in either order and trailing comments (#9290)", () => { + const indicators = ["|", ">", "|-", ">-", "|+", ">+", "|2", ">2", "|-2", "|2-", ">2+", ">2-"]; + for (const indicator of indicators) { + const src = [ + "---", + `description: ${indicator}`, + " title: not-a-real-key", + "title: Real", + "title: DupReal", + "---", + "", + "body", + ].join("\n"); + expect(findDuplicateFrontmatterKeys(src)).toEqual(["title"]); + } + for (const indicator of ["|-", ">", "|2-"]) { + const src = [ + "---", + `description: ${indicator} # sources below`, + " title: not-a-real-key", + "title: Real", + "title: DupReal", + "---", + "", + "body", + ].join("\n"); + expect(findDuplicateFrontmatterKeys(src)).toEqual(["title"]); + } + }); }); describe("normalizeUrl edge cases (via extractContentDuplicateSignals)", () => {