diff --git a/src/exportMap/childContext.js b/src/exportMap/childContext.js index 3534c5913..cf8135f12 100644 --- a/src/exportMap/childContext.js +++ b/src/exportMap/childContext.js @@ -1,7 +1,7 @@ import { hashObject } from 'eslint-module-utils/hash'; -let parserOptionsHash = ''; -let prevParserOptions = ''; +let optionsHash = ''; +let prevOptions = ''; let settingsHash = ''; let prevSettings = ''; @@ -17,13 +17,35 @@ export default function childContext(path, context) { prevSettings = JSON.stringify(settings); } - if (JSON.stringify(parserOptions) !== prevParserOptions) { - parserOptionsHash = hashObject({ parserOptions }).digest('hex'); - prevParserOptions = JSON.stringify(parserOptions); + // We'll use either a combination of `parserOptions` and `parserPath` or `languageOptions` + // to construct the cache key, depending on whether this is using a flat config or not. + let optionsToken; + if (!parserPath && languageOptions) { + // Replacer function helps us with serializing the parser nested within `languageOptions`. + const replacerFn = (_, value) => { + if (typeof value === 'function') { + return value.toString(); + } + return value; + }; + if (JSON.stringify(languageOptions, replacerFn) !== prevOptions) { + optionsHash = hashObject({ languageOptions }).digest('hex'); + prevOptions = JSON.stringify(languageOptions, replacerFn); + } + // For languageOptions, we're just using the hashed options as the options token + optionsToken = optionsHash; + } else { + if (JSON.stringify(parserOptions) !== prevOptions) { + optionsHash = hashObject({ parserOptions }).digest('hex'); + prevOptions = JSON.stringify(parserOptions); + } + // When not using flat config, we use a combination of the hashed parserOptions + // and parserPath as the token + optionsToken = String(parserPath) + optionsHash; } return { - cacheKey: String(parserPath) + parserOptionsHash + settingsHash + String(path), + cacheKey: optionsToken + settingsHash + String(path), settings, parserOptions, parserPath, diff --git a/tests/src/exportMap/childContext.js b/tests/src/exportMap/childContext.js index 06fa04afe..16652f9dc 100644 --- a/tests/src/exportMap/childContext.js +++ b/tests/src/exportMap/childContext.js @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import { hashObject } from 'eslint-module-utils/hash'; import childContext from '../../../src/exportMap/childContext'; @@ -16,8 +17,13 @@ describe('childContext', () => { const languageOptions = { ecmaVersion: 2024, sourceType: 'module', - parser: {}, + parser: { + parseForESLint() {}, + }, }; + const languageOptionsHash = hashObject({ languageOptions }).digest('hex'); + const parserOptionsHash = hashObject({ parserOptions }).digest('hex'); + const settingsHash = hashObject({ settings }).digest('hex'); // https://github.com/import-js/eslint-plugin-import/issues/3051 it('should pass context properties through, if present', () => { @@ -48,4 +54,69 @@ describe('childContext', () => { expect(result.path).to.equal(path); expect(result.cacheKey).to.be.a('string'); }); + + it('should construct cache key out of languageOptions if present', () => { + const mockContext = { + settings, + languageOptions, + }; + + const result = childContext(path, mockContext); + + expect(result.cacheKey).to.equal(languageOptionsHash + settingsHash + path); + }); + + it('should use the same cache key upon multiple calls', () => { + const mockContext = { + settings, + languageOptions, + }; + + let result = childContext(path, mockContext); + + const expectedCacheKey = languageOptionsHash + settingsHash + path; + expect(result.cacheKey).to.equal(expectedCacheKey); + + result = childContext(path, mockContext); + expect(result.cacheKey).to.equal(expectedCacheKey); + }); + + it('should update cacheKey if different languageOptions are passed in', () => { + const mockContext = { + settings, + languageOptions, + }; + + let result = childContext(path, mockContext); + + const firstCacheKey = languageOptionsHash + settingsHash + path; + expect(result.cacheKey).to.equal(firstCacheKey); + + // Second run with different parser + mockContext.languageOptions = { + ...languageOptions, + parser: { + parseForESLint() {}, + parse() {}, + }, + }; + + result = childContext(path, mockContext); + + const secondCacheKey = hashObject({ languageOptions: mockContext.languageOptions }).digest('hex') + settingsHash + path; + expect(result.cacheKey).to.not.equal(firstCacheKey); + expect(result.cacheKey).to.equal(secondCacheKey); + }); + + it('should construct cache key out of parserOptions and parserPath if no languageOptions', () => { + const mockContext = { + settings, + parserOptions, + parserPath, + }; + + const result = childContext(path, mockContext); + + expect(result.cacheKey).to.equal(String(parserPath) + parserOptionsHash + settingsHash + path); + }); });