Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Fixed
- **checkBrokenLinks false positives from HTML comments** — `checkBrokenLinks` now skips links inside HTML comments (`<!-- ... -->`), both single-line and multi-line. Previously, template examples in comments (e.g., in `patterns/INDEX.md`) were flagged as broken links.

## [0.6.1] - 2026-06-14

### Added
Expand Down
4 changes: 3 additions & 1 deletion src/drift/checkers/broken-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ export function checkBrokenLinks(
}

const fileDir = dirname(filePath);
const lines = content.split("\n");
// Strip complete HTML comments before line processing. Unclosed <!-- stays
// as plain text (matches checkIndexSync behavior).
const lines = content.replace(/<!--[\s\S]*?-->/g, "").split("\n");
let inFence = false;

for (let i = 0; i < lines.length; i++) {
Expand Down
46 changes: 46 additions & 0 deletions test/checkers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -595,4 +595,50 @@ describe("checkBrokenLinks", () => {
expect(issues).toHaveLength(1);
expect(issues[0].severity).toBe("warning");
});

it("ignores links inside HTML comments", () => {
const file = join(tmpDir, "INDEX.md");
writeFileSync(
file,
"# Index\n\n<!-- Example: [foo.md](foo.md) -->\n\n| Pattern | Use |\n|---|---|\n",
);
const issues = checkBrokenLinks([file], tmpDir, tmpDir);
expect(issues).toHaveLength(0);
});

it("ignores links inside multi-line HTML comments", () => {
const file = join(tmpDir, "INDEX.md");
writeFileSync(
file,
"# Index\n\n<!--\n[foo.md](foo.md)\n[bar.md](bar.md)\n-->\n\nReal content.\n",
);
const issues = checkBrokenLinks([file], tmpDir, tmpDir);
expect(issues).toHaveLength(0);
});

it("scans links after an unclosed HTML comment as plain text", () => {
mkdirSync(join(tmpDir, "context"), { recursive: true });
const file = join(tmpDir, "guide.md");
// Unclosed <!-- stays as plain text; link after it should still be checked
writeFileSync(
file,
"# Guide\n\n<!-- this comment never closes\n\nSee [missing](./context/nowhere.md).\n",
);
const issues = checkBrokenLinks([file], tmpDir, tmpDir);
expect(issues).toHaveLength(1);
expect(issues[0].message).toContain("nowhere.md");
});

it("scans links on lines with inline HTML comments", () => {
mkdirSync(join(tmpDir, "context"), { recursive: true });
writeFileSync(join(tmpDir, "context/target.md"), "# Target\n");
const file = join(tmpDir, "guide.md");
// Link before comment should be scanned; link inside comment should not
writeFileSync(
file,
"[real](./context/target.md) <!-- [fake](./missing.md) -->\n",
);
const issues = checkBrokenLinks([file], tmpDir, tmpDir);
expect(issues).toHaveLength(0);
});
});