UNPKG

prettier-eslint

Version:

Formats your JavaScript using prettier followed by eslint --fix

342 lines (332 loc) 10.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getESLint = getESLint; exports.getOptionsForFormatting = getOptionsForFormatting; exports.requireModule = requireModule; var _commonTags = require("common-tags"); var _dlv = _interopRequireDefault(require("dlv")); var _loglevelColoredLevelPrefix = _interopRequireDefault(require("loglevel-colored-level-prefix")); var _eslint = require("eslint"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /* eslint import/no-dynamic-require:0 */ const logger = (0, _loglevelColoredLevelPrefix.default)({ prefix: 'prettier-eslint' }); const RULE_DISABLED = {}; const RULE_NOT_CONFIGURED = 'RULE_NOT_CONFIGURED'; const ruleValueExists = prettierRuleValue => prettierRuleValue !== RULE_NOT_CONFIGURED && prettierRuleValue !== RULE_DISABLED && typeof prettierRuleValue !== 'undefined'; const OPTION_GETTERS = { printWidth: { ruleValue: rules => getRuleValue(rules, 'max-len', 'code'), ruleValueToPrettierOption: getPrintWidth }, tabWidth: { ruleValue: rules => { let value = getRuleValue(rules, 'indent'); if (value === 'tab') { value = getRuleValue(rules, 'max-len', 'tabWidth'); } return value; }, ruleValueToPrettierOption: getTabWidth }, singleQuote: { ruleValue: rules => getRuleValue(rules, 'quotes'), ruleValueToPrettierOption: getSingleQuote }, trailingComma: { ruleValue: rules => getRuleValue(rules, 'comma-dangle', []), ruleValueToPrettierOption: getTrailingComma }, bracketSpacing: { ruleValue: rules => getRuleValue(rules, 'object-curly-spacing'), ruleValueToPrettierOption: getBracketSpacing }, semi: { ruleValue: rules => getRuleValue(rules, 'semi'), ruleValueToPrettierOption: getSemi }, useTabs: { ruleValue: rules => getRuleValue(rules, 'indent'), ruleValueToPrettierOption: getUseTabs }, bracketSameLine: { ruleValue: rules => getRuleValue(rules, 'react/jsx-closing-bracket-location', 'nonEmpty'), ruleValueToPrettierOption: getBracketSameLine }, arrowParens: { ruleValue: rules => getRuleValue(rules, 'arrow-parens'), ruleValueToPrettierOption: getArrowParens } }; /* eslint import/prefer-default-export:0 */ function getOptionsForFormatting(eslintConfig, prettierOptions = {}, fallbackPrettierOptions = {}) { const eslint = getRelevantESLintConfig(eslintConfig); const prettier = getPrettierOptionsFromESLintRules(eslintConfig, prettierOptions, fallbackPrettierOptions); return { eslint, prettier }; } function getRelevantESLintConfig(eslintConfig) { const linter = new _eslint.Linter(); const rules = linter.getRules(); logger.debug('turning off unfixable rules'); const relevantRules = {}; rules.forEach((rule, name) => { const { meta: { fixable } } = rule; if (!fixable) { logger.trace('turning off rule:', JSON.stringify({ [name]: rule })); rule = ['off']; relevantRules[name] = rule; } }, {}); return { // defaults useEslintrc: false, ...eslintConfig, // overrides rules: { ...eslintConfig.rules, ...relevantRules }, fix: true, globals: eslintConfig.globals || {} }; } /** * This accepts an eslintConfig object and converts * it to the `prettier` options object */ function getPrettierOptionsFromESLintRules(eslintConfig, prettierOptions, fallbackPrettierOptions) { const { rules } = eslintConfig; const prettierPluginOptions = getRuleValue(rules, 'prettier/prettier', []); if (ruleValueExists(prettierPluginOptions)) { prettierOptions = { ...prettierPluginOptions, ...prettierOptions }; } return Object.keys(OPTION_GETTERS).reduce((options, key) => configureOptions(prettierOptions, fallbackPrettierOptions, key, options, rules), prettierOptions); } // If an ESLint rule that prettier can be configured with is enabled create a // prettier configuration object that reflects the ESLint rule configuration. function configureOptions(prettierOptions, fallbackPrettierOptions, key, options, rules) { const givenOption = prettierOptions[key]; const optionIsGiven = givenOption !== undefined; if (optionIsGiven) { options[key] = givenOption; } else { const { ruleValue, ruleValueToPrettierOption } = OPTION_GETTERS[key]; const eslintRuleValue = ruleValue(rules); const option = ruleValueToPrettierOption(eslintRuleValue, fallbackPrettierOptions, rules); if (option !== undefined) { options[key] = option; } } return options; } function getPrintWidth(eslintValue, fallbacks) { return makePrettierOption('printWidth', eslintValue, fallbacks); } function getTabWidth(eslintValue, fallbacks) { return makePrettierOption('tabWidth', eslintValue, fallbacks); } function getSingleQuote(eslintValue, fallbacks) { let prettierValue; if (eslintValue === 'single') { prettierValue = true; } else if (eslintValue === 'double') { prettierValue = false; } else if (eslintValue === 'backtick') { prettierValue = false; } else { prettierValue = eslintValue; } return makePrettierOption('singleQuote', prettierValue, fallbacks); } function getTrailingComma(eslintValue, fallbacks) { let prettierValue; if (eslintValue === 'never') { prettierValue = 'none'; } else if (typeof eslintValue === 'string' && eslintValue.indexOf('always') === 0) { prettierValue = 'es5'; } else if (typeof eslintValue === 'object') { prettierValue = getValFromTrailingCommaConfig(eslintValue); } else { prettierValue = RULE_NOT_CONFIGURED; } return makePrettierOption('trailingComma', prettierValue, fallbacks); } function getValFromTrailingCommaConfig(objectConfig) { const { arrays = '', objects = '', functions = '' } = objectConfig; const fns = isAlways(functions); const es5 = [arrays, objects].some(isAlways); if (fns) { return 'all'; } else if (es5) { return 'es5'; } else { return 'none'; } } function getBracketSpacing(eslintValue, fallbacks) { let prettierValue; if (eslintValue === 'never') { prettierValue = false; } else if (eslintValue === 'always') { prettierValue = true; } else { prettierValue = eslintValue; } return makePrettierOption('bracketSpacing', prettierValue, fallbacks); } function getSemi(eslintValue, fallbacks) { let prettierValue; if (eslintValue === 'never') { prettierValue = false; } else if (eslintValue === 'always') { prettierValue = true; } else { prettierValue = eslintValue; } return makePrettierOption('semi', prettierValue, fallbacks); } function getUseTabs(eslintValue, fallbacks) { let prettierValue; if (eslintValue === 'tab') { prettierValue = true; } else { prettierValue = RULE_NOT_CONFIGURED; } return makePrettierOption('useTabs', prettierValue, fallbacks); } function getBracketSameLine(eslintValue, fallbacks) { let prettierValue; if (eslintValue === 'after-props') { prettierValue = true; } else if (eslintValue === 'tag-aligned' || eslintValue === 'line-aligned' || eslintValue === 'props-aligned') { prettierValue = false; } else { prettierValue = eslintValue; } return makePrettierOption('bracketSameLine', prettierValue, fallbacks); } function getArrowParens(eslintValue, fallbacks) { let prettierValue; if (eslintValue === 'as-needed') { prettierValue = 'avoid'; } else { prettierValue = eslintValue; } return makePrettierOption('arrowParens', prettierValue, fallbacks); } function extractRuleValue(objPath, name, value) { // XXX: Ignore code coverage for the following else case // There are currently no eslint rules which we can infer prettier // options from, that have an object option which we don't know how // to infer from. // istanbul ignore else if (objPath) { logger.trace((0, _commonTags.oneLine)` Getting the value from object configuration of ${name}. delving into ${JSON.stringify(value)} with path "${objPath}" `); return (0, _dlv.default)(value, objPath, RULE_NOT_CONFIGURED); } // istanbul ignore next logger.debug((0, _commonTags.oneLine)` The ${name} rule is using an object configuration of ${JSON.stringify(value)} but prettier-eslint is not currently capable of getting the prettier value based on an object configuration for ${name}. Please file an issue (and make a pull request?) `); // istanbul ignore next return undefined; } function getRuleValue(rules, name, objPath) { const ruleConfig = rules[name]; if (Array.isArray(ruleConfig)) { const [ruleSetting, value] = ruleConfig; // If `ruleSetting` is set to disable the ESLint rule don't use `value` as // it might be a value provided by an overriden config package e.g. airbnb // overriden by config-prettier. The airbnb values are provided even though // config-prettier disables the rule. Instead use fallback or prettier // default. if (ruleSetting === 0 || ruleSetting === 'off') { return RULE_DISABLED; } if (typeof value === 'object') { return extractRuleValue(objPath, name, value); } else { logger.trace((0, _commonTags.oneLine)` The ${name} rule is configured with a non-object value of ${value}. Using that value. `); return value; } } return RULE_NOT_CONFIGURED; } function isAlways(val) { return val.indexOf('always') === 0; } function makePrettierOption(prettierRuleName, prettierRuleValue, fallbacks) { if (ruleValueExists(prettierRuleValue)) { return prettierRuleValue; } const fallback = fallbacks[prettierRuleName]; if (typeof fallback !== 'undefined') { logger.debug((0, _commonTags.oneLine)` The ${prettierRuleName} rule is not configured, using provided fallback of ${fallback} `); return fallback; } logger.debug((0, _commonTags.oneLine)` The ${prettierRuleName} rule is not configured, let prettier decide `); return undefined; } function requireModule(modulePath, name) { try { logger.trace(`requiring "${name}" module at "${modulePath}"`); return require(modulePath); } catch (error) { logger.error((0, _commonTags.oneLine)` There was trouble getting "${name}". Is "${modulePath}" a correct path to the "${name}" module? `); throw error; } } function getESLint(eslintPath, eslintOptions) { const { ESLint } = requireModule(eslintPath, 'eslint'); try { return new ESLint(eslintOptions); } catch (error) { logger.error('There was trouble creating the ESLint CLIEngine.'); throw error; } }