Skip to content
Merged
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
18 changes: 16 additions & 2 deletions src/review/content-lane/duplicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ export type ContentDuplicateReview = {
relatedCandidates: ContentDuplicateMatch[];
};

function stripYamlComment(value: string): string {
return value.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+`. Mirrors source-evidence.ts (#8016).
const BLOCK_SCALAR_INDICATOR = /^[|>](?:[+-]?\d?|\d[+-]?)$/;

// True when a raw frontmatter value is a block-scalar header. Comment-tolerant: a header may carry a trailing inline
// comment (`| # sources below`), which normalizes away before the indicator is matched.
function isBlockScalarHeader(raw: string): boolean {
return BLOCK_SCALAR_INDICATOR.test(stripYamlComment(raw));
}

function unquoteYamlScalar(value: string): string {
const trimmed = value.trim();
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
Expand Down Expand Up @@ -74,7 +88,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 +143,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
64 changes: 64 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,27 @@ describe("parseSimpleFrontmatter", () => {
expect(f.title).toBe("Real");
expect(Object.keys(f)).toEqual(["title"]);
});

it("reads a |2- block-scalar header (digit-before-chomp order) and its indented body (#9290)", () => {
const src = ["---", "description: |2-", " line one", " line two", "title: T", "---", "", "body"].join("\n");
const f = parseSimpleFrontmatter(src);
expect(f.description).toBe("line one\nline two");
expect(f.title).toBe("T");
});

it("reads a |- # comment block-scalar header and its indented body (#9290)", () => {
const src = ["---", "description: |- # trailing note", " hidden content", "title: T", "---", "", "body"].join("\n");
const f = parseSimpleFrontmatter(src);
expect(f.description).toBe("hidden content");
expect(f.title).toBe("T");
});

it("reads a > # comment folded block-scalar header and joins its indented body (#9290)", () => {
const src = ["---", "description: > # folded note", " word one", " word two", "title: T", "---", "", "body"].join("\n");
const f = parseSimpleFrontmatter(src);
expect(f.description).toBe("word one word two");
expect(f.title).toBe("T");
});
});

describe("findDuplicateFrontmatterKeys", () => {
Expand Down Expand Up @@ -283,6 +304,49 @@ 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 a |2- block-scalar body without false-flagging indented pseudo-keys (#9290)", () => {
const src = [
"---",
"description: |2-",
" title: not-a-real-key",
" another: line",
"title: Real",
"title: DupReal",
"---",
"",
"body",
].join("\n");
expect(findDuplicateFrontmatterKeys(src)).toEqual(["title"]);
});

it("skips a |- # comment block-scalar body without false-flagging indented pseudo-keys (#9290)", () => {
const src = [
"---",
"description: |- # trailing note",
" title: not-a-real-key",
"slug: a",
"slug: b",
"---",
"",
"body",
].join("\n");
expect(findDuplicateFrontmatterKeys(src)).toEqual(["slug"]);
});

it("skips a > # comment folded block-scalar body without false-flagging indented pseudo-keys (#9290)", () => {
const src = [
"---",
"description: > # folded note",
" title: not-a-real-key",
"category: x",
"category: y",
"---",
"",
"body",
].join("\n");
expect(findDuplicateFrontmatterKeys(src)).toEqual(["category"]);
});
});

describe("normalizeUrl edge cases (via extractContentDuplicateSignals)", () => {
Expand Down
Loading