@@ -16,6 +16,7 @@ import type { HeftConfiguration } from '@rushstack/heft';
1616
1717import { LinterBase , type ILinterBaseOptions } from './LinterBase' ;
1818import type { IExtendedSourceFile } from './internalTypings/TypeScriptInternals' ;
19+ import { name as pluginName , version as pluginVersion } from '../package.json' ;
1920
2021interface IEslintOptions extends ILinterBaseOptions {
2122 eslintPackage : typeof TEslint | typeof TEslintLegacy ;
@@ -61,32 +62,13 @@ function getFormattedErrorMessage(
6162 return lintMessage . ruleId ? `(${ lintMessage . ruleId } ) ${ lintMessage . message } ` : lintMessage . message ;
6263}
6364
64- interface IExtendedEslintConfig extends TEslint . Linter . Config {
65- // https://github.com/eslint/eslint/blob/d6fa4ac031c2fe24fb778e84940393fbda3ddf77/lib/config/config.js#L264
66- toJSON : ( ) => object ;
67- __originalToJSON : ( ) => object ;
68- }
69-
70- function patchedToJSON ( this : IExtendedEslintConfig ) : object {
71- // If the input config has a parserOptions.programs property, we need to recreate it
72- // as a non-enumerable property so that it does not get serialized, as it is not
73- // serializable.
74- if (
75- this . languageOptions ?. parserOptions ?. programs &&
76- this . languageOptions . parserOptions . propertyIsEnumerable ( 'programs' )
77- ) {
78- let { programs } = this . languageOptions . parserOptions ;
79- Object . defineProperty ( this . languageOptions . parserOptions , 'programs' , {
80- get : ( ) => programs ,
81- set : ( value : TTypescript . Program [ ] ) => {
82- programs = value ;
83- } ,
84- enumerable : false
85- } ) ;
86- }
87-
88- const serializableConfig : object = this . __originalToJSON . call ( this ) ;
89- return serializableConfig ;
65+ function parserOptionsToJson ( this : TEslint . Linter . LanguageOptions [ 'parserOptions' ] ) : object {
66+ const serializableParserOptions : TEslint . Linter . LanguageOptions [ 'parserOptions' ] = {
67+ ...this ,
68+ // Remove the programs to avoid circular references and non-serializable data
69+ programs : undefined
70+ } ;
71+ return serializableParserOptions ;
9072}
9173
9274const ESLINT_CONFIG_JS_FILENAME : string = 'eslint.config.js' ;
@@ -112,6 +94,7 @@ export class Eslint extends LinterBase<TEslint.ESLint.LintResult | TEslintLegacy
11294 ( TEslint . Linter . LintMessage | TEslintLegacy . Linter . LintMessage ) [ ]
11395 > = new Map ( ) ;
11496 private readonly _sarifLogPath : string | undefined ;
97+ private readonly _configHashMap : WeakMap < object , string > = new WeakMap ( ) ;
11598
11699 protected constructor ( options : IEslintOptions ) {
117100 super ( 'eslint' , options ) ;
@@ -165,21 +148,37 @@ export class Eslint extends LinterBase<TEslint.ESLint.LintResult | TEslintLegacy
165148 // if we're not fixing.
166149 const legacyEslintOverrideConfig : TEslintLegacy . Linter . Config = {
167150 parserOptions : {
168- programs : [ tsProgram ]
151+ programs : [ tsProgram ] ,
152+ toJSON : parserOptionsToJson
169153 }
170154 } ;
171155 overrideConfig = legacyEslintOverrideConfig ;
172156 } else {
157+ let overrideParserOptions : TEslint . Linter . ParserOptions = {
158+ programs : [ tsProgram ] ,
159+ // Used by stableStringify and ESLint > 9.28.0
160+ toJSON : parserOptionsToJson
161+ } ;
162+ if ( this . _eslintPackageVersion . minor < 28 ) {
163+ overrideParserOptions = Object . defineProperties ( overrideParserOptions , {
164+ // Support for `toJSON` within languageOptions was added in ESLint 9.28.0
165+ // This hack tells ESLint's `languageOptionsToJSON` function to replace the entire `parserOptions` object with `@rushstack/heft-lint-plugin@${version}`
166+ meta : {
167+ value : {
168+ name : pluginName ,
169+ version : pluginVersion
170+ }
171+ }
172+ } ) ;
173+ }
173174 // The @typescript -eslint/parser package allows providing an existing TypeScript program to avoid needing
174175 // to reparse. However, fixers in ESLint run in multiple passes against the underlying code until the
175176 // fix fully succeeds. This conflicts with providing an existing program as the code no longer maps to
176177 // the provided program, producing garbage fix output. To avoid this, only provide the existing program
177178 // if we're not fixing.
178179 const eslintOverrideConfig : TEslint . Linter . Config = {
179180 languageOptions : {
180- parserOptions : {
181- programs : [ tsProgram ]
182- }
181+ parserOptions : overrideParserOptions
183182 }
184183 } ;
185184 overrideConfig = eslintOverrideConfig ;
@@ -254,18 +253,10 @@ export class Eslint extends LinterBase<TEslint.ESLint.LintResult | TEslintLegacy
254253 }
255254
256255 protected override async getSourceFileHashAsync ( sourceFile : IExtendedSourceFile ) : Promise < string > {
257- const sourceFileEslintConfiguration : IExtendedEslintConfig = await this . _linter . calculateConfigForFile (
256+ const sourceFileEslintConfiguration : TEslint . Linter . Config = await this . _linter . calculateConfigForFile (
258257 sourceFile . fileName
259258 ) ;
260259
261- // The eslint configuration object contains a toJSON() method that returns a serializable version of the
262- // configuration. However, we are manually injecting the TypeScript program into the parserOptions, which
263- // is not serializable. Patch the function to remove the program before returning the serializable version.
264- if ( sourceFileEslintConfiguration . toJSON && ! sourceFileEslintConfiguration . __originalToJSON ) {
265- sourceFileEslintConfiguration . __originalToJSON = sourceFileEslintConfiguration . toJSON ;
266- sourceFileEslintConfiguration . toJSON = patchedToJSON . bind ( sourceFileEslintConfiguration ) ;
267- }
268-
269260 const hash : Hash = createHash ( 'sha1' ) ;
270261 // Use a stable stringifier to ensure that the hash is always the same, even if the order of the properties
271262 // changes. This is also done in ESLint
0 commit comments