diff --git a/src/getESLint.js b/src/getESLint.js index c55915d..ecaec3c 100644 --- a/src/getESLint.js +++ b/src/getESLint.js @@ -5,10 +5,20 @@ const { Worker: JestWorker } = require('jest-worker'); // @ts-ignore const { setup, lintFiles } = require('./worker'); const { getESLintOptions } = require('./options'); -const { jsonStringifyReplacerSortKeys } = require('./utils'); -/** @type {{[key: string]: any}} */ -const cache = {}; +/** @type {Map} */ +const cache = new Map(); + +class CacheKey { + /** + * @param {string|undefined} key + * @param {Options} options + */ + constructor(key, options) { + this.key = key; + this.options = options; + } +} /** @typedef {import('eslint').ESLint} ESLint */ /** @typedef {import('eslint').ESLint.LintResult} LintResult */ @@ -46,7 +56,7 @@ async function loadESLint(options) { * @returns {Promise} */ async function loadESLintThreaded(key, poolSize, options) { - const cacheKey = getCacheKey(key, options); + const cacheKey = new CacheKey(key, options); const { eslintPath = 'eslint' } = options; const source = require.resolve('./worker'); const workerOptions = { @@ -73,7 +83,7 @@ async function loadESLintThreaded(key, poolSize, options) { (worker && (await worker.lintFiles(files))) || /* istanbul ignore next */ [], cleanup: async () => { - cache[cacheKey] = local; + cache.set(cacheKey, local); context.lintFiles = (files) => local.lintFiles(files); if (worker) { worker.end(); @@ -99,23 +109,16 @@ async function getESLint(key, { threads, ...options }) { : /* istanbul ignore next */ threads; - const cacheKey = getCacheKey(key, { threads, ...options }); - if (!cache[cacheKey]) { - cache[cacheKey] = + const cacheKey = new CacheKey(key, { threads, ...options }); + if (!cache.has(cacheKey)) { + cache.set( + cacheKey, max > 1 ? await loadESLintThreaded(key, max, options) - : await loadESLint(options); + : await loadESLint(options), + ); } - return cache[cacheKey]; -} - -/** - * @param {string|undefined} key - * @param {Options} options - * @returns {string} - */ -function getCacheKey(key, options) { - return JSON.stringify({ key, options }, jsonStringifyReplacerSortKeys); + return cache.get(cacheKey); } module.exports = { diff --git a/src/utils.js b/src/utils.js index b9ebc36..abac4a0 100644 --- a/src/utils.js +++ b/src/utils.js @@ -89,29 +89,8 @@ function parseFoldersToGlobs(patterns, extensions = []) { }); } -/** - * @param {string} _ key, but unused - * @param {any} value - */ -const jsonStringifyReplacerSortKeys = (_, value) => { - /** - * @param {{ [x: string]: any; }} sorted - * @param {string | number} key - */ - const insert = (sorted, key) => { - // eslint-disable-next-line no-param-reassign - sorted[key] = value[key]; - return sorted; - }; - - return value instanceof Object && !(value instanceof Array) - ? Object.keys(value).sort().reduce(insert, {}) - : value; -}; - module.exports = { arrify, parseFiles, parseFoldersToGlobs, - jsonStringifyReplacerSortKeys, }; diff --git a/test/circular-plugin.test.js b/test/circular-plugin.test.js new file mode 100644 index 0000000..972bcf1 --- /dev/null +++ b/test/circular-plugin.test.js @@ -0,0 +1,34 @@ +import pack from './utils/pack'; + +describe('circular plugin', () => { + it('should support plugins with circular configs', async () => { + const plugin = { + configs: {}, + rules: {}, + processors: {}, + }; + + Object.assign(plugin.configs, { + recommended: { + plugins: { + self: plugin, + }, + rules: {}, + }, + }); + + const loaderOptions = { + configType: 'flat', + overrideConfig: { + plugins: { plugin: plugin }, + }, + overrideConfigFile: true, + }; + + const compiler = pack('good', loaderOptions); + + const stats = await compiler.runAsync(); + expect(stats.hasWarnings()).toBe(false); + expect(stats.hasErrors()).toBe(false); + }); +});