You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
207 lines
5.5 KiB
207 lines
5.5 KiB
#!/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;
|
|
|