xo
Version:
JavaScript/TypeScript linter (ESLint wrapper) with great defaults
110 lines • 4.49 kB
JavaScript
import arrify from 'arrify';
import eslintConfigXo from 'eslint-config-xo';
import { xoToEslintConfigItem } from './utils.js';
const getGlobalOptions = (xoConfig) => {
const options = {};
for (const xoConfigItem of xoConfig) {
const hasScopedOptions = xoConfigItem.files !== undefined
|| xoConfigItem.basePath !== undefined
|| xoConfigItem.ignores !== undefined;
const hasStyleOptions = xoConfigItem.space !== undefined
|| xoConfigItem.semicolon !== undefined
|| xoConfigItem.prettier !== undefined;
if (hasScopedOptions && hasStyleOptions) {
throw new TypeError('XO style options only support global config items. Use ESLint rules for file-scoped overrides.');
}
if (xoConfigItem.space !== undefined) {
options.space = xoConfigItem.space;
}
if (xoConfigItem.semicolon !== undefined) {
options.semicolon = xoConfigItem.semicolon;
}
if (xoConfigItem.prettier !== undefined) {
options.prettier = xoConfigItem.prettier;
}
}
return options;
};
/**
Merge all plugins from every config into a single config entry at the start of the array, ensuring user-provided plugins take precedence. This avoids ESLint's flat config rejecting duplicate plugin names.
*/
const hoistPlugins = (configs, userPluginOverrides) => {
const plugins = {};
const configsWithoutPlugins = [];
for (const configItem of configs) {
const { plugins: configPlugins } = configItem;
if (!configPlugins) {
configsWithoutPlugins.push(configItem);
continue;
}
// ESLint flat config rejects duplicate plugin names, so merge all plugins into one config.
Object.assign(plugins, configPlugins);
const { plugins: _ignored, ...configWithoutPlugins } = configItem;
// A leftover with only `files` has no effect, so drop it.
if (Object.keys(configWithoutPlugins).some(key => key !== 'files')) {
configsWithoutPlugins.push(configWithoutPlugins);
}
}
for (const [pluginName, plugin] of userPluginOverrides) {
plugins[pluginName] = plugin;
}
if (Object.keys(plugins).length === 0) {
return configsWithoutPlugins;
}
return [
{
name: 'xo/plugins',
plugins,
},
...configsWithoutPlugins,
];
};
/**
Takes an XO flat config and returns an ESLint flat config.
*/
export function xoToEslintConfig(flatXoConfig) {
const xoConfig = flatXoConfig ?? [];
const baseConfig = eslintConfigXo(getGlobalOptions(xoConfig));
const prettierConfig = baseConfig.find(config => config.name === 'xo/prettier' || config.name === 'xo/prettier-compat');
if (prettierConfig) {
// XO's global Prettier option applies to every language config, not only the JavaScript and TypeScript files covered by eslint-config-xo.
delete prettierConfig.files;
}
const userPluginOverrides = new Map();
for (const xoConfigItem of xoConfig) {
const { plugins } = xoConfigItem;
if (plugins) {
for (const [pluginName, plugin] of Object.entries(plugins)) {
userPluginOverrides.set(pluginName, plugin);
}
}
const keysOfXoConfig = Object.keys(xoConfigItem);
if (keysOfXoConfig.length === 0) {
continue;
}
/**
Special case global ignores
*/
if (xoConfigItem.ignores !== undefined) {
if (keysOfXoConfig.length === 1) {
baseConfig.push({ ignores: arrify(xoConfigItem.ignores) });
continue;
}
if (keysOfXoConfig.length === 2 && xoConfigItem.name !== undefined) {
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 (Object.keys(eslintConfigItem).length > 0) {
baseConfig.push(eslintConfigItem);
}
}
// User plugins should always win, even if XO injects plugins later in the config list.
return hoistPlugins(baseConfig, userPluginOverrides);
}
export default xoToEslintConfig;
//# sourceMappingURL=xo-to-eslint.js.map