UNPKG

5.64 kBJavaScriptView Raw
1#!/usr/bin/env node
2
3"use strict";
4
5const fs = require("fs");
6const path = require("path");
7const getStdin = require("get-stdin");
8const validators = require("./validators");
9
10const SPECIAL_RULES_URL =
11 "https://github.com/prettier/eslint-config-prettier#special-rules";
12
13if (module === require.main) {
14 if (process.argv.length > 2 || process.stdin.isTTY) {
15 console.error(
16 [
17 "This tool checks whether an ESLint configuration contains rules that are",
18 "unnecessary or conflict with Prettier. It’s supposed to be run like this:",
19 "",
20 " npx eslint --print-config path/to/main.js | npx eslint-config-prettier-check",
21 " npx eslint --print-config test/index.js | npx eslint-config-prettier-check",
22 "",
23 "Exit codes:",
24 "",
25 "0: No automatically detectable problems found.",
26 "1: Unexpected error.",
27 "2: Conflicting rules found.",
28 "",
29 "For more information, see:",
30 "https://github.com/prettier/eslint-config-prettier#cli-helper-tool",
31 ].join("\n")
32 );
33 process.exit(1);
34 }
35
36 getStdin()
37 .then((string) => {
38 const result = processString(string);
39 if (result.stderr) {
40 console.error(result.stderr);
41 }
42 if (result.stdout) {
43 console.error(result.stdout);
44 }
45 process.exit(result.code);
46 })
47 .catch((error) => {
48 console.error("Unexpected error", error);
49 process.exit(1);
50 });
51}
52
53function processString(string) {
54 let config;
55 try {
56 config = JSON.parse(string);
57 } catch (error) {
58 return {
59 stderr: `Failed to parse JSON:\n${error.message}`,
60 code: 1,
61 };
62 }
63
64 if (
65 !(
66 Object.prototype.toString.call(config) === "[object Object]" &&
67 Object.prototype.toString.call(config.rules) === "[object Object]"
68 )
69 ) {
70 return {
71 stderr: `Expected a \`{"rules: {...}"}\` JSON object, but got:\n${string}`,
72 code: 1,
73 };
74 }
75
76 // This used to look at "files" in package.json, but that is not reliable due
77 // to an npm bug. See:
78 // https://github.com/prettier/eslint-config-prettier/issues/57
79 const allRules = Object.assign(
80 Object.create(null),
81 ...fs
82 .readdirSync(path.join(__dirname, ".."))
83 .filter((name) => !name.startsWith(".") && name.endsWith(".js"))
84 .map((ruleFileName) => require(`../${ruleFileName}`).rules)
85 );
86
87 const regularRules = filterRules(
88 allRules,
89 (ruleName, value) => value === "off"
90 );
91 const optionsRules = filterRules(
92 allRules,
93 (ruleName, value) => value === 0 && ruleName in validators
94 );
95 const specialRules = filterRules(
96 allRules,
97 (ruleName, value) => value === 0 && !(ruleName in validators)
98 );
99
100 const flaggedRules = Object.keys(config.rules)
101 .map((ruleName) => {
102 const value = config.rules[ruleName];
103 const arrayValue = Array.isArray(value) ? value : [value];
104 const level = arrayValue[0];
105 const options = arrayValue.slice(1);
106 const isOff = level === "off" || level === 0;
107 return !isOff && ruleName in allRules ? { ruleName, options } : null;
108 })
109 .filter(Boolean);
110
111 const regularFlaggedRuleNames = filterRuleNames(
112 flaggedRules,
113 (ruleName) => ruleName in regularRules
114 );
115 const optionsFlaggedRuleNames = filterRuleNames(
116 flaggedRules,
117 (ruleName, options) =>
118 ruleName in optionsRules && !validators[ruleName](options)
119 );
120 const specialFlaggedRuleNames = filterRuleNames(
121 flaggedRules,
122 (ruleName) => ruleName in specialRules
123 );
124
125 if (
126 regularFlaggedRuleNames.length === 0 &&
127 optionsFlaggedRuleNames.length === 0
128 ) {
129 const baseMessage =
130 "No rules that are unnecessary or conflict with Prettier were found.";
131
132 const message =
133 specialFlaggedRuleNames.length === 0
134 ? baseMessage
135 : [
136 baseMessage,
137 "",
138 "However, the following rules are enabled but cannot be automatically checked. See:",
139 SPECIAL_RULES_URL,
140 "",
141 printRuleNames(specialFlaggedRuleNames),
142 ].join("\n");
143
144 return {
145 stdout: message,
146 code: 0,
147 };
148 }
149
150 const regularMessage = [
151 "The following rules are unnecessary or might conflict with Prettier:",
152 "",
153 printRuleNames(regularFlaggedRuleNames),
154 ].join("\n");
155
156 const optionsMessage = [
157 "The following rules are enabled with options that might conflict with Prettier. See:",
158 SPECIAL_RULES_URL,
159 "",
160 printRuleNames(optionsFlaggedRuleNames),
161 ].join("\n");
162
163 const specialMessage = [
164 "The following rules are enabled but cannot be automatically checked. See:",
165 SPECIAL_RULES_URL,
166 "",
167 printRuleNames(specialFlaggedRuleNames),
168 ].join("\n");
169
170 const message = [
171 regularFlaggedRuleNames.length === 0 ? null : regularMessage,
172 optionsFlaggedRuleNames.length === 0 ? null : optionsMessage,
173 specialFlaggedRuleNames.length === 0 ? null : specialMessage,
174 ]
175 .filter(Boolean)
176 .join("\n\n");
177
178 return {
179 stdout: message,
180 code: 2,
181 };
182}
183
184function filterRules(rules, fn) {
185 return Object.keys(rules)
186 .filter((ruleName) => fn(ruleName, rules[ruleName]))
187 .reduce((obj, ruleName) => {
188 obj[ruleName] = true;
189 return obj;
190 }, Object.create(null));
191}
192
193function filterRuleNames(rules, fn) {
194 return rules
195 .filter((rule) => fn(rule.ruleName, rule.options))
196 .map((rule) => rule.ruleName);
197}
198
199function printRuleNames(ruleNames) {
200 return ruleNames
201 .slice()
202 .sort()
203 .map((ruleName) => `- ${ruleName}`)
204 .join("\n");
205}
206
207exports.processString = processString;