1 | #!/usr/bin/env node
|
2 |
|
3 | "use strict";
|
4 |
|
5 | const fs = require("fs");
|
6 | const path = require("path");
|
7 | const getStdin = require("get-stdin");
|
8 | const validators = require("./validators");
|
9 |
|
10 | const SPECIAL_RULES_URL =
|
11 | "https://github.com/prettier/eslint-config-prettier#special-rules";
|
12 |
|
13 | if (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 | " eslint --print-config path/to/main.js | eslint-config-prettier-check",
|
21 | " eslint --print-config test/index.js | 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 |
|
53 | function 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 |
|
77 |
|
78 |
|
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 |
|
184 | function 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 |
|
193 | function filterRuleNames(rules, fn) {
|
194 | return rules
|
195 | .filter(rule => fn(rule.ruleName, rule.options))
|
196 | .map(rule => rule.ruleName);
|
197 | }
|
198 |
|
199 | function printRuleNames(ruleNames) {
|
200 | return ruleNames
|
201 | .slice()
|
202 | .sort()
|
203 | .map(ruleName => `- ${ruleName}`)
|
204 | .join("\n");
|
205 | }
|
206 |
|
207 | exports.processString = processString;
|