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.
236 lines
6.6 KiB
236 lines
6.6 KiB
#!/usr/bin/env node |
|
|
|
"use strict"; |
|
|
|
const validators = require("./validators"); |
|
const config = require(".."); |
|
const prettier = require("../prettier"); |
|
|
|
// Require locally installed eslint, for `npx eslint-config-prettier` support |
|
// with no local eslint-config-prettier installation. |
|
const localRequire = (request) => |
|
require( |
|
require.resolve(request, { |
|
paths: [process.cwd(), ...require.resolve.paths("eslint")], |
|
}) |
|
); |
|
|
|
let experimentalApi = {}; |
|
try { |
|
experimentalApi = localRequire("eslint/use-at-your-own-risk"); |
|
// eslint-disable-next-line unicorn/prefer-optional-catch-binding |
|
} catch (_error) {} |
|
|
|
const { ESLint, FlatESLint = experimentalApi.FlatESLint } = |
|
localRequire("eslint"); |
|
|
|
const SPECIAL_RULES_URL = |
|
"https://github.com/prettier/eslint-config-prettier#special-rules"; |
|
|
|
const PRETTIER_RULES_URL = |
|
"https://github.com/prettier/eslint-config-prettier#arrow-body-style-and-prefer-arrow-callback"; |
|
|
|
if (module === require.main) { |
|
const args = process.argv.slice(2); |
|
|
|
if (args.length === 0) { |
|
console.error(help()); |
|
process.exit(1); |
|
} |
|
|
|
const eslint = new ESLint(); |
|
const flatESLint = FlatESLint === undefined ? undefined : new FlatESLint(); |
|
|
|
Promise.all( |
|
args.map((file) => { |
|
switch (process.env.ESLINT_USE_FLAT_CONFIG) { |
|
case "true": { |
|
return flatESLint.calculateConfigForFile(file); |
|
} |
|
case "false": { |
|
return eslint.calculateConfigForFile(file); |
|
} |
|
default: { |
|
// This turns synchronous errors (such as `.calculateConfigForFile` not existing) |
|
// and turns them into promise rejections. |
|
return Promise.resolve() |
|
.then(() => flatESLint.calculateConfigForFile(file)) |
|
.catch(() => eslint.calculateConfigForFile(file)); |
|
} |
|
} |
|
}) |
|
) |
|
.then((configs) => { |
|
const rules = configs.flatMap(({ rules }, index) => |
|
Object.entries(rules).map((entry) => [...entry, args[index]]) |
|
); |
|
const result = processRules(rules); |
|
if (result.stderr) { |
|
console.error(result.stderr); |
|
} |
|
if (result.stdout) { |
|
console.error(result.stdout); |
|
} |
|
process.exit(result.code); |
|
}) |
|
.catch((error) => { |
|
console.error(error.message); |
|
process.exit(1); |
|
}); |
|
} |
|
|
|
function help() { |
|
return ` |
|
Usage: npx eslint-config-prettier FILE... |
|
|
|
Resolves an ESLint configuration for every given FILE and checks if they |
|
contain rules that are unnecessary or conflict with Prettier. Example: |
|
|
|
npx eslint-config-prettier index.js test/index.js other/file/to/check.js |
|
|
|
Exit codes: |
|
|
|
0: No automatically detectable problems found. |
|
1: General error. |
|
2: Conflicting rules found. |
|
|
|
For more information, see: |
|
https://github.com/prettier/eslint-config-prettier#cli-helper-tool |
|
`.trim(); |
|
} |
|
|
|
function processRules(configRules) { |
|
const regularRules = filterRules(config.rules, (_, value) => value === "off"); |
|
const optionsRules = filterRules( |
|
config.rules, |
|
(ruleName, value) => value === 0 && ruleName in validators |
|
); |
|
const specialRules = filterRules( |
|
config.rules, |
|
(ruleName, value) => value === 0 && !(ruleName in validators) |
|
); |
|
|
|
const enabledRules = configRules |
|
.map(([ruleName, value, source]) => { |
|
const arrayValue = Array.isArray(value) ? value : [value]; |
|
const [level, ...options] = arrayValue; |
|
const isOff = level === "off" || level === 0; |
|
return isOff ? null : { ruleName, options, source }; |
|
}) |
|
.filter(Boolean); |
|
|
|
const flaggedRules = enabledRules.filter( |
|
({ ruleName }) => ruleName in config.rules |
|
); |
|
|
|
const regularFlaggedRuleNames = filterRuleNames( |
|
flaggedRules, |
|
({ ruleName }) => ruleName in regularRules |
|
); |
|
const optionsFlaggedRuleNames = filterRuleNames( |
|
flaggedRules, |
|
({ ruleName, ...rule }) => |
|
ruleName in optionsRules && !validators[ruleName](rule) |
|
); |
|
const specialFlaggedRuleNames = filterRuleNames( |
|
flaggedRules, |
|
({ ruleName }) => ruleName in specialRules |
|
); |
|
const prettierFlaggedRuleNames = filterRuleNames( |
|
enabledRules, |
|
({ ruleName, source }) => |
|
ruleName in prettier.rules && |
|
enabledRules.some( |
|
(rule) => |
|
rule.ruleName === "prettier/prettier" && rule.source === source |
|
) |
|
); |
|
|
|
const regularMessage = [ |
|
"The following rules are unnecessary or might conflict with Prettier:", |
|
"", |
|
printRuleNames(regularFlaggedRuleNames), |
|
].join("\n"); |
|
|
|
const optionsMessage = [ |
|
"The following rules are enabled with config 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 prettierMessage = [ |
|
"The following rules can cause issues when using eslint-plugin-prettier at the same time.", |
|
"Only enable them if you know what you are doing! See:", |
|
PRETTIER_RULES_URL, |
|
"", |
|
printRuleNames(prettierFlaggedRuleNames), |
|
].join("\n"); |
|
|
|
if ( |
|
regularFlaggedRuleNames.length === 0 && |
|
optionsFlaggedRuleNames.length === 0 |
|
) { |
|
const message = |
|
specialFlaggedRuleNames.length === 0 && |
|
prettierFlaggedRuleNames.length === 0 |
|
? "No rules that are unnecessary or conflict with Prettier were found." |
|
: [ |
|
specialFlaggedRuleNames.length === 0 ? null : specialMessage, |
|
prettierFlaggedRuleNames.length === 0 ? null : prettierMessage, |
|
"Other than that, no rules that are unnecessary or conflict with Prettier were found.", |
|
] |
|
.filter(Boolean) |
|
.join("\n\n"); |
|
|
|
return { |
|
stdout: message, |
|
code: 0, |
|
}; |
|
} |
|
|
|
const message = [ |
|
regularFlaggedRuleNames.length === 0 ? null : regularMessage, |
|
optionsFlaggedRuleNames.length === 0 ? null : optionsMessage, |
|
specialFlaggedRuleNames.length === 0 ? null : specialMessage, |
|
prettierFlaggedRuleNames.length === 0 ? null : prettierMessage, |
|
] |
|
.filter(Boolean) |
|
.join("\n\n"); |
|
|
|
return { |
|
stdout: message, |
|
code: 2, |
|
}; |
|
} |
|
|
|
function filterRules(rules, fn) { |
|
return Object.fromEntries( |
|
Object.entries(rules) |
|
.filter(([ruleName, value]) => fn(ruleName, value)) |
|
.map(([ruleName]) => [ruleName, true]) |
|
); |
|
} |
|
|
|
function filterRuleNames(rules, fn) { |
|
return [ |
|
...new Set(rules.filter((rule) => fn(rule)).map((rule) => rule.ruleName)), |
|
]; |
|
} |
|
|
|
function printRuleNames(ruleNames) { |
|
return ruleNames |
|
.slice() |
|
.sort() |
|
.map((ruleName) => `- ${ruleName}`) |
|
.join("\n"); |
|
} |
|
|
|
exports.processRules = processRules;
|
|
|