xo
Version:
JavaScript/TypeScript linter (ESLint wrapper) with great defaults
113 lines • 5.89 kB
JavaScript
/* eslint-disable complexity */
import configXoTypescript from 'eslint-config-xo-typescript';
import arrify from 'arrify';
import configReact from 'eslint-config-xo-react';
import pluginPrettier from 'eslint-plugin-prettier';
import eslintConfigPrettier from 'eslint-config-prettier';
import { config } from './config.js';
import { xoToEslintConfigItem } from './utils.js';
/**
Takes a XO flat config and returns an ESlint flat config.
*/
export function xoToEslintConfig(flatXoConfig, { prettierOptions = {} } = {}) {
const baseConfig = [...config];
/**
Since configs are merged and the last config takes precedence this means we need to handle both true AND false cases for each option. For example, we need to turn `prettier`, `space`, `semi`, etc. on or off for a specific file.
*/
for (const xoConfigItem of flatXoConfig ?? []) {
const keysOfXoConfig = Object.keys(xoConfigItem);
if (keysOfXoConfig.length === 0) {
continue;
}
/** Special case global ignores */
if (xoConfigItem.ignores) {
if (keysOfXoConfig.length === 1) {
baseConfig.push({ ignores: arrify(xoConfigItem.ignores) });
continue;
}
else if (keysOfXoConfig.length === 2 && xoConfigItem.name) {
baseConfig.push({ name: xoConfigItem.name, ignores: arrify(xoConfigItem.ignores) });
continue;
}
}
/**
An ESLint config item derived from the XO config item with rules and files initialized.
*/
const eslintConfigItem = xoToEslintConfigItem(xoConfigItem);
if (xoConfigItem.semicolon === false) {
eslintConfigItem.rules['@stylistic/semi'] = ['error', 'never'];
eslintConfigItem.rules['@stylistic/semi-spacing'] = [
'error',
{ before: false, after: true },
];
}
if (xoConfigItem.space) {
const spaces = typeof xoConfigItem.space === 'number' ? xoConfigItem.space : 2;
eslintConfigItem.rules['@stylistic/indent'] = [
'error',
spaces,
// eslint-disable-next-line @typescript-eslint/naming-convention
{ SwitchCase: 1 },
];
}
else if (xoConfigItem.space === false) {
// If a user sets this to false for a small subset of files for some reason,
// then we need to set them back to their original values.
eslintConfigItem.rules['@stylistic/indent'] = configXoTypescript[1]?.rules?.['@stylistic/indent'];
}
if (xoConfigItem.react) {
// Ensure the files applied to the React config are the same as the config they are derived from
baseConfig.push({ ...configReact[0], files: eslintConfigItem.files });
}
// Prettier should generally be the last config in the array
if (xoConfigItem.prettier) {
if (xoConfigItem.prettier === 'compat') {
baseConfig.push({ ...eslintConfigPrettier, files: eslintConfigItem.files });
}
else {
// Validate that Prettier options match other `xoConfig` options.
if ((xoConfigItem.semicolon && prettierOptions.semi === false) ?? (!xoConfigItem.semicolon && prettierOptions.semi === true)) {
throw new Error(`The Prettier config \`semi\` is ${prettierOptions.semi} while Xo \`semicolon\` is ${xoConfigItem.semicolon}, also check your .editorconfig for inconsistencies.`);
}
if (((xoConfigItem.space ?? typeof xoConfigItem.space === 'number') && prettierOptions.useTabs === true) || (!xoConfigItem.space && prettierOptions.useTabs === false)) {
throw new Error(`The Prettier config \`useTabs\` is ${prettierOptions.useTabs} while Xo \`space\` is ${xoConfigItem.space}, also check your .editorconfig for inconsistencies.`);
}
if (typeof xoConfigItem.space === 'number' && typeof prettierOptions.tabWidth === 'number' && xoConfigItem.space !== prettierOptions.tabWidth) {
throw new Error(`The Prettier config \`tabWidth\` is ${prettierOptions.tabWidth} while Xo \`space\` is ${xoConfigItem.space}, also check your .editorconfig for inconsistencies.`);
}
// Add Prettier plugin
eslintConfigItem.plugins = {
...eslintConfigItem.plugins,
prettier: pluginPrettier,
};
const prettierConfig = {
singleQuote: true,
bracketSpacing: false,
bracketSameLine: false,
trailingComma: 'all',
tabWidth: typeof xoConfigItem.space === 'number' ? xoConfigItem.space : 2,
useTabs: !xoConfigItem.space,
semi: xoConfigItem.semicolon,
...prettierOptions,
};
// Configure Prettier rules
const rulesWithPrettier = {
...eslintConfigItem.rules,
...pluginPrettier.configs?.['recommended']?.rules,
// eslint-disable-next-line @typescript-eslint/naming-convention
'prettier/prettier': ['error', prettierConfig],
...eslintConfigPrettier.rules,
};
eslintConfigItem.rules = rulesWithPrettier;
}
}
else if (xoConfigItem.prettier === false) {
// Turn Prettier off for a subset of files
eslintConfigItem.rules['prettier/prettier'] = 'off';
}
baseConfig.push(eslintConfigItem);
}
return baseConfig;
}
export default xoToEslintConfig;
//# sourceMappingURL=xo-to-eslint.js.map