/* eslint-disable no-useless-escape */ 'use strict'; const chalk = require('chalk'); const glob = require('glob'); const fs = require('fs'); const path = require('path'); const writeGood = require('write-good'); let errorCount = 0; let warningCount = 0; const errors = {}; const warnings = {}; let pages = glob.sync('docs/**/*.md'); pages.forEach(function checkPage (pagePath) { var match; var referencedMatch; var referencingMatch; var referencingRegex; var urlRegex; let content = fs.readFileSync(pagePath, 'utf-8'); // Check prose with `write-good`. checkProse(pagePath, content); // Inline links: `[link](../page.md)` let inlineLinkRegex = /\[.*?\]\((.*?)\)/g; match = inlineLinkRegex.exec(content); while (match !== null) { checkLink(pagePath, match[1], 'Page does not exist'); match = inlineLinkRegex.exec(content); } // Referenced links: `[link][page] -> [page]: ../page.md` let referencedLinkRegex = /\[.*?\]\[(.*?)\]/g; match = referencedLinkRegex.exec(content); while (match !== null) { urlRegex = new RegExp(`\\[${match[1]}\\]: \(\.\*\)`, 'g'); referencedMatch = urlRegex.exec(content); if (referencedMatch) { if (!checkLink(pagePath, referencedMatch[1])) { addError(pagePath, referencedMatch[0], 'Page does not exist'); } } else { addError(pagePath, match[0], 'Link definition does not exist'); } match = referencedLinkRegex.exec(content); } // Unused defined links: `[page]: ../page.md -> [*][page]` let referenceRegex = new RegExp(`\\[\(\.\*\)\?\\]: \.\*`, 'g'); match = referenceRegex.exec(content); while (match !== null) { referencingRegex = new RegExp(`\\[${match[1]}\\]`, 'g'); referencingMatch = referencingRegex.exec(content); if (!referencingMatch) { addError(pagePath, match[1], 'Link definition not being used'); } match = referenceRegex.exec(content); } }); function checkLink (pagePath, url) { var absPath; var content; var hash; var headingMatch; var headingRegex; var headingIds; var urlPath; // Relative path. if (url.indexOf('.') === 0) { // Check page exists. urlPath = url.split('#')[0]; absPath = path.resolve(pagePath, `../${urlPath}`); if (!fs.existsSync(absPath)) { return false; } if (url.indexOf('#') === -1) { return true; } // Check hash / anchor heading. headingIds = []; hash = url.split('#')[1]; headingRegex = /#+\s+(.*?)\n/g; content = fs.readFileSync(absPath, 'utf-8'); headingMatch = headingRegex.exec(content); while (headingMatch !== null) { headingIds.push(convertHeading(headingMatch[1])); headingMatch = headingRegex.exec(content); } return headingIds.indexOf(hash) !== -1; } return true; } function convertHeading (heading) { return heading .replace(/#+\s+/, '') .toLowerCase() .replace(/`\./g, '') // Remove leading dot. .replace(/[`*\(\),]/g, '') // Remove special characters. .replace(/[^\w]+/g, '-') .replace(/-$/g, ''); } function checkProse (pagePath, content) { content = content.replace(/^---[\s\S]*?---/g, ''); // Remove metadata. content = content.replace(/(\|.*\|)+/g, ''); // Remove tables. content = content.replace(/```[\s\S]*?```/g, ''); // Remove code blocks. content = content.replace(/`[\s\S]*?`/g, ''); // Remove inline code. content = content.replace(/\n#+.*/g, ''); // Remove headings. writeGood(content, {adverb: false, illusion: false}).forEach(function add (error) { addWarning( pagePath, `...${content.substring(error.index - 10, error.index + error.offset + 10).replace(/\n/g, '')}...`, error.reason); }); } function addError (pagePath, str, message) { if (!errors[pagePath]) { errors[pagePath] = []; } errors[pagePath].push({ message: message, str: str }); errorCount++; } function addWarning (pagePath, str, message) { if (!warnings[pagePath]) { warnings[pagePath] = []; } warnings[pagePath].push({ message: message, str: str }); warningCount++; } Object.keys(warnings).forEach(function (pagePath) { console.log(chalk.yellow.bold(`\n[warn]: ${pagePath}`)); warnings[pagePath].forEach(function (warning) { console.log(chalk.yellow(` ${warning.message}: `) + `${warning.str}`); }); }); Object.keys(errors).forEach(function (pagePath) { console.log(chalk.red.bold(`\n[error]: ${pagePath}`)); errors[pagePath].forEach(function (error) { console.log(chalk.red(` ${error.message}: `) + `${error.str}`); }); }); console.log(chalk.yellow.bold(`${warningCount} warnings`)); console.log(chalk.red.bold(`${errorCount} errors`)); // Fail. if (errorCount) { process.exit(1); }