UNPKG

xo

Version:

JavaScript/TypeScript linter (ESLint wrapper) with great defaults

110 lines • 4.49 kB
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