diff --git a/.changeset/short-bees-sell.md b/.changeset/short-bees-sell.md new file mode 100644 index 000000000..395358633 --- /dev/null +++ b/.changeset/short-bees-sell.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-import-x": patch +--- + +add languageOptions to ChildContext diff --git a/package.json b/package.json index d454b12cd..171e8ee6b 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "dependencies": { "@typescript-eslint/utils": "^7.4.0", "debug": "^4.3.4", + "dequal": "^2.0.3", "doctrine": "^3.0.0", "eslint-import-resolver-node": "^0.3.9", "get-tsconfig": "^4.7.3", diff --git a/src/types.ts b/src/types.ts index 71a7d8362..78a947787 100644 --- a/src/types.ts +++ b/src/types.ts @@ -79,6 +79,7 @@ export type ChildContext = { settings: PluginSettings parserPath?: string | null parserOptions?: TSESLint.ParserOptions + languageOptions?: TSESLint.FlatConfig.LanguageOptions path: string filename?: string } diff --git a/src/utils/export-map.ts b/src/utils/export-map.ts index 32cec83cf..f4e823a02 100644 --- a/src/utils/export-map.ts +++ b/src/utils/export-map.ts @@ -3,6 +3,7 @@ import path from 'node:path' import type { TSESLint, TSESTree } from '@typescript-eslint/utils' import debug from 'debug' +import { dequal } from 'dequal' import type { Annotation } from 'doctrine' import doctrine from 'doctrine' import type { AST } from 'eslint' @@ -1078,11 +1079,6 @@ export function recursivePatternCapture( } } -let parserOptionsHash = '' -let prevParserOptions = '' -let settingsHash = '' -let prevSettings = '' - /** * don't hold full context object in memory, just grab what we need. * also calculate a cacheKey, where parts of the cacheKey hash are memoized @@ -1091,24 +1087,14 @@ function childContext( path: string, context: RuleContext | ChildContext, ): ChildContext { - const { settings, parserOptions, parserPath } = context - - if (JSON.stringify(settings) !== prevSettings) { - settingsHash = hashObject({ settings }).digest('hex') - prevSettings = JSON.stringify(settings) - } - - if (JSON.stringify(parserOptions) !== prevParserOptions) { - parserOptionsHash = hashObject({ parserOptions }).digest('hex') - prevParserOptions = JSON.stringify(parserOptions) - } + const { settings, parserOptions, parserPath, languageOptions } = context return { - cacheKey: - String(parserPath) + parserOptionsHash + settingsHash + String(path), + cacheKey: makeContextCacheKey(context) + String(path), settings, parserOptions, parserPath, + languageOptions, path, filename: 'physicalFilename' in context @@ -1117,6 +1103,50 @@ function childContext( } } +type OptionsVersionsCache = Record< + 'settings' | 'parserOptions' | 'parser', + { value: unknown; version: number } +> + +const optionsVersionsCache: OptionsVersionsCache = { + settings: { value: null, version: 0 }, + parserOptions: { value: null, version: 0 }, + parser: { value: null, version: 0 }, +} + +function getOptionsVersion(key: keyof OptionsVersionsCache, value: unknown) { + const entry = optionsVersionsCache[key] + + if (!dequal(value, entry.value)) { + entry.value = value + entry.version += 1 + } + + return String(entry.version) +} + +function makeContextCacheKey(context: RuleContext | ChildContext) { + const { settings, parserPath, parserOptions, languageOptions } = context + + let hash = getOptionsVersion('settings', settings) + + const usedParserOptions = languageOptions?.parserOptions ?? parserOptions + + hash += getOptionsVersion('parserOptions', usedParserOptions) + + if (languageOptions) { + const { ecmaVersion, sourceType } = languageOptions + hash += String(ecmaVersion) + String(sourceType) + } + + hash += getOptionsVersion( + 'parser', + parserPath ?? languageOptions?.parser?.meta ?? languageOptions?.parser, + ) + + return hash +} + /** * sometimes legacy support isn't _that_ hard... right? */