Last active
September 8, 2017 10:11
-
-
Save estliberitas/bfd35c7a5a977aa5d221 to your computer and use it in GitHub Desktop.
Script checking that all links with references are present in Markdown file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
// Usage | |
// node check-markdown-links.js path/to/markdown/file.md | |
// What it does: | |
// - Warns when `[ref][]` or `[title][ref]` style links don't have references defined on a page | |
// - Warns when `[ref]: URL` reference is not used anywhere | |
// - Warns when two `[ref]: URL` references point to same `URL` | |
// - Warns when `[ref]: URL` reference is defined in non-lexical order | |
const fs = require('fs'); | |
const path = require('path'); | |
const filePath = path.resolve(process.cwd(), process.argv[2]); | |
const lines = fs.readFileSync(filePath, 'utf-8').split(/\n\r?/); | |
const linkMap = new Map(); | |
const links = []; | |
let previousTitle = null; | |
for (let l = 0; l < lines.length; l++) { | |
const line = lines[l].trim(); | |
if (!line) { | |
continue; | |
} | |
else if (line[0] === '#') { | |
continue; | |
} | |
let pos = line.indexOf('['); | |
while (line[pos] === '[') { | |
let end; | |
if (line[pos + 1] === '`') { | |
const codeEnd = line.indexOf('`', pos + 2); | |
end = line.indexOf(']', codeEnd); | |
} | |
else { | |
end = line.indexOf(']', pos); | |
} | |
if (end === -1) { | |
break; | |
} | |
const title = line.substring(pos + 1, end); | |
if (line[end + 1] === '[') { | |
// [link][...] | |
pos = end + 1; | |
end = line.indexOf(']', pos); | |
if (end === pos + 1) { | |
// [link][] | |
if (!linkMap.get(title)) { | |
linkMap.set(title, [{ line: l + 1 }]); | |
} | |
else { | |
linkMap.get(title).push({ line: l + 1 }); | |
} | |
} | |
else if (end > pos + 1) { | |
// [link][ref] | |
const ref = line.substring(pos + 1, end); | |
if (!linkMap.get(ref)) { | |
linkMap.set(ref, [{ original: title, line: l + 1 }]); | |
} | |
else { | |
linkMap.get(ref).push({ original: title, line: l + 1 }); | |
} | |
} | |
} | |
else if (line[end + 1] === ':') { | |
if (previousTitle && title.toLowerCase() < previousTitle.toLowerCase()) { | |
console.warn(`Link ${title} is defined in non-lexical order`); | |
} | |
previousTitle = title; | |
links[title] = line.substring(end + 2).trim(); | |
} | |
pos = line.indexOf('[', end); | |
} | |
} | |
const keys = Object.keys(links); | |
for (let o = 0; o < keys.length; o++) { | |
const keyO = keys[o]; | |
const url = links[keyO]; | |
for (let i = o + 1; i < keys.length; i++) { | |
const keyI = keys[i]; | |
if (url === links[keyI]) { | |
console.warn(`Duplicate URL ${url} in links:`); | |
console.warn(` ${keyO}`); | |
console.warn(` ${keyI}`); | |
} | |
} | |
} | |
for (const key of linkMap.keys()) { | |
const occurrences = linkMap.get(key); | |
if (!links.hasOwnProperty(key)) { | |
console.error(`No link with id \`${key}\`. Occurrences:`); | |
occurrences.forEach((o) => { | |
let out = ` line: ${o.line}`; | |
if (o.original) { | |
out += ` (original title: ${o.original})`; | |
} | |
console.error(out); | |
}); | |
} | |
else { | |
linkMap.delete(key); | |
delete links[key]; | |
} | |
} | |
Object.keys(links).forEach((link) => { | |
console.warn(`Unused link ${link}`); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment