vanduynslagerp/stylelint-config-pretty

View on GitHub
test/helpers/rule-finder.js

Summary

Maintainability
A
0 mins
Test Coverage
import path from 'path';
import fs from 'fs';
import pEvent from 'p-event';
import {flattenDeep, difference} from 'lodash';
import stylelint from 'stylelint';

const RULE_PATHS = {
  stylelint: 'stylelint/lib/rules',
  'stylelint-scss': 'stylelint-scss/src/rules',
  'stylelint-order': 'stylelint-order/rules',
};

/**
 * @typedef {Object} RuleFinder
 * @property {Array<string>} available All the available, non deprecated rules (including plugins referenced in the config file).
 * @property {Array<string>} current All the rules configured in the config file.
 * @property {Array<string>} unused All the available, non deprecated rules that are not present in the config file.
 * @property {Array<string>} unknown All rules present in the config file that are deprecated or doesn't exists.
 */

/**
 * Retrieve available, current and unused rules for a given stylelint config file.
 *
 * @method getRuleFinder
 * @param {string} config path to the config file to test, relative to `cwd`.
 * @return {RuleFinder} Available, current, unused and unknown rules for the config file.
 */
export default async function getRuleFinder(config) {
  const available = await getAvailableRules(path.join(process.cwd(), config));
  const current = Object.keys(
    (await stylelint.createLinter()._extendExplorer.load(process.cwd(), config)).config.rules
  );

  return {
    available,
    current,
    unused: difference(available, current),
    unknown: difference(current, available),
  };
}

/**
 * Get all the available, non deprecated rules (including plugins) for the given config file.
 *
 * @method getAvailableRules
 * @param {string} config The absolute path to the config file to analyze.
 * @return {Array<string>} All the available, non deprecated rules (including plugins referenced in the config file).
 */
async function getAvailableRules(config) {
  const rules = await Promise.all(
    Object.keys(stylelint.rules).map(rule => filterDeprecated(`${RULE_PATHS.stylelint}/${rule}`, rule))
  );
  const configObject = require(config);

  if (configObject.plugins) {
    rules.push(await Promise.all(configObject.plugins.map(getPluginRules)));
  }
  return flattenDeep(rules).filter(rule => Boolean(rule));
}

/**
 * Get all the available, non deprecated rules for the given plugin.
 *
 * @method getPluginRules
 * @param {string} pluginName npm name of the stylelint plugin
 * @return {Array<string>} All the available, non deprecated rules for the plugin.
 */
function getPluginRules(pluginName) {
  const pluginObject = require(pluginName);

  return Promise.all(
    (pluginObject.default ? pluginObject.default : pluginObject).map(ruleObject =>
      filterDeprecated(`${RULE_PATHS[pluginName]}/${ruleObject.ruleName.split('/')[1]}`, ruleObject.ruleName)
    )
  );
}

/**
 * Determine if a rule is deprecated. Consider a rule deprecated if it documentation contains '**deprecated.'
 *
 * @method filterDeprecated
 * @param {string} rulesPath Path of the directory containing the rules definition, relative to `cwd`.
 * @param {string} rule Rule name (including plugin prefix).
 * @return {Promise<string>} a Promise that resolve to the rule name if not deprecated, to `null` otherwise.
 */
function filterDeprecated(rulesPath, rule) {
  const stream = fs.createReadStream(path.resolve(process.cwd(), 'node_modules', `${rulesPath}/README.md`), {
    highWaterMark: 1024,
  });

  return pEvent(stream, 'data').then(chunk => (/\*\*deprecated/i.test(chunk.toString()) ? null : rule));
}