#!/usr/bin/env node "use strict"; const fs = require("fs"); const path = require("path"); const getStdin = require("get-stdin"); const validators = require("./validators"); const SPECIAL_RULES_URL = "https://github.com/prettier/eslint-config-prettier#special-rules"; if (module === require.main) { if (process.argv.length > 2 || process.stdin.isTTY) { console.error( [ "This tool checks whether an ESLint configuration contains rules that are", "unnecessary or conflict with Prettier. It’s supposed to be run like this:", "", " npx eslint --print-config path/to/main.js | npx eslint-config-prettier-check", " npx eslint --print-config test/index.js | npx eslint-config-prettier-check", "", "Exit codes:", "", "0: No automatically detectable problems found.", "1: Unexpected error.", "2: Conflicting rules found.", "", "For more information, see:", "https://github.com/prettier/eslint-config-prettier#cli-helper-tool", ].join("\n") ); process.exit(1); } getStdin() .then((string) => { const result = processString(string); if (result.stderr) { console.error(result.stderr); } if (result.stdout) { console.error(result.stdout); } process.exit(result.code); }) .catch((error) => { console.error("Unexpected error", error); process.exit(1); }); } function processString(string) { let config; try { config = JSON.parse(string); } catch (error) { return { stderr: `Failed to parse JSON:\n${error.message}`, code: 1, }; } if ( !( Object.prototype.toString.call(config) === "[object Object]" && Object.prototype.toString.call(config.rules) === "[object Object]" ) ) { return { stderr: `Expected a \`{"rules: {...}"}\` JSON object, but got:\n${string}`, code: 1, }; } // This used to look at "files" in package.json, but that is not reliable due // to an npm bug. See: // https://github.com/prettier/eslint-config-prettier/issues/57 const allRules = Object.assign( Object.create(null), ...fs .readdirSync(path.join(__dirname, "..")) .filter((name) => !name.startsWith(".") && name.endsWith(".js")) .map((ruleFileName) => require(`../${ruleFileName}`).rules) ); const regularRules = filterRules( allRules, (ruleName, value) => value === "off" ); const optionsRules = filterRules( allRules, (ruleName, value) => value === 0 && ruleName in validators ); const specialRules = filterRules( allRules, (ruleName, value) => value === 0 && !(ruleName in validators) ); const flaggedRules = Object.keys(config.rules) .map((ruleName) => { const value = config.rules[ruleName]; const arrayValue = Array.isArray(value) ? value : [value]; const level = arrayValue[0]; const options = arrayValue.slice(1); const isOff = level === "off" || level === 0; return !isOff && ruleName in allRules ? { ruleName, options } : null; }) .filter(Boolean); const regularFlaggedRuleNames = filterRuleNames( flaggedRules, (ruleName) => ruleName in regularRules ); const optionsFlaggedRuleNames = filterRuleNames( flaggedRules, (ruleName, options) => ruleName in optionsRules && !validators[ruleName](options) ); const specialFlaggedRuleNames = filterRuleNames( flaggedRules, (ruleName) => ruleName in specialRules ); if ( regularFlaggedRuleNames.length === 0 && optionsFlaggedRuleNames.length === 0 ) { const baseMessage = "No rules that are unnecessary or conflict with Prettier were found."; const message = specialFlaggedRuleNames.length === 0 ? baseMessage : [ baseMessage, "", "However, the following rules are enabled but cannot be automatically checked. See:", SPECIAL_RULES_URL, "", printRuleNames(specialFlaggedRuleNames), ].join("\n"); return { stdout: message, code: 0, }; } const regularMessage = [ "The following rules are unnecessary or might conflict with Prettier:", "", printRuleNames(regularFlaggedRuleNames), ].join("\n"); const optionsMessage = [ "The following rules are enabled with options that might conflict with Prettier. See:", SPECIAL_RULES_URL, "", printRuleNames(optionsFlaggedRuleNames), ].join("\n"); const specialMessage = [ "The following rules are enabled but cannot be automatically checked. See:", SPECIAL_RULES_URL, "", printRuleNames(specialFlaggedRuleNames), ].join("\n"); const message = [ regularFlaggedRuleNames.length === 0 ? null : regularMessage, optionsFlaggedRuleNames.length === 0 ? null : optionsMessage, specialFlaggedRuleNames.length === 0 ? null : specialMessage, ] .filter(Boolean) .join("\n\n"); return { stdout: message, code: 2, }; } function filterRules(rules, fn) { return Object.keys(rules) .filter((ruleName) => fn(ruleName, rules[ruleName])) .reduce((obj, ruleName) => { obj[ruleName] = true; return obj; }, Object.create(null)); } function filterRuleNames(rules, fn) { return rules .filter((rule) => fn(rule.ruleName, rule.options)) .map((rule) => rule.ruleName); } function printRuleNames(ruleNames) { return ruleNames .slice() .sort() .map((ruleName) => `- ${ruleName}`) .join("\n"); } exports.processString = processString;