Newer
Older
reroad-test / 2020-ryusei / aframe-master / scripts / docsLint.js
@ryusei ryusei on 22 Oct 2020 4 KB パノラマ表示
/* 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); }