Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions src/review/content-lane/duplicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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("'"))) {
Expand All @@ -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
Expand All @@ -74,7 +87,7 @@ export function parseSimpleFrontmatter(source: string): Record<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)) {
// 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 */
Expand Down Expand Up @@ -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 === "") {
Expand Down
46 changes: 46 additions & 0 deletions test/unit/content-lane-duplicates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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)", () => {
Expand Down
Loading