@@ -5,6 +5,8 @@ import * as path from 'node:path';
55import { performance } from 'node:perf_hooks' ;
66import { createHash , type Hash } from 'node:crypto' ;
77
8+ import type * as TTypescript from 'typescript' ;
9+
810import { FileSystem , JsonFile , Path } from '@rushstack/node-core-library' ;
911import type { ITerminal } from '@rushstack/terminal' ;
1012import type { IScopedLogger } from '@rushstack/heft' ;
@@ -51,6 +53,12 @@ interface ILinterCacheData {
5153 * each array item is the file's path and the second element is the file's hash.
5254 */
5355 fileVersions : [ string , string ] [ ] ;
56+
57+ /**
58+ * A hash of the list of filenames that were linted. This is used to verify that
59+ * the cache was run with the same files.
60+ */
61+ filesHash ?: string ;
5462}
5563
5664export abstract class LinterBase < TLintResult > {
@@ -85,14 +93,40 @@ export abstract class LinterBase<TLintResult> {
8593
8694 const relativePaths : Map < string , string > = new Map ( ) ;
8795
88- const fileHash : Hash = createHash ( 'md5' ) ;
96+ // Collect and sort file paths for stable hashing
97+ const relativePathsArray : string [ ] = [ ] ;
8998 for ( const file of options . typeScriptFilenames ) {
9099 // Need to use relative paths to ensure portability.
91100 const relative : string = Path . convertToSlashes ( path . relative ( commonDirectory , file ) ) ;
92101 relativePaths . set ( file , relative ) ;
93- fileHash . update ( relative ) ;
102+ relativePathsArray . push ( relative ) ;
103+ }
104+ relativePathsArray . sort ( ) ;
105+
106+ // Calculate the hash of the list of filenames for verification purposes
107+ const filesHash : Hash = createHash ( 'md5' ) ;
108+ for ( const relative of relativePathsArray ) {
109+ filesHash . update ( relative ) ;
110+ }
111+ const filesHashString : string = filesHash . digest ( 'base64url' ) ;
112+
113+ // Calculate the hash suffix based on the project-relative path of the tsconfig file
114+ // Extract the config file path from the program's compiler options
115+ const compilerOptions : TTypescript . CompilerOptions = options . tsProgram . getCompilerOptions ( ) ;
116+ const tsconfigFilePath : string | undefined = compilerOptions . configFilePath as string | undefined ;
117+
118+ let hashSuffix : string ;
119+ if ( tsconfigFilePath ) {
120+ const relativeTsconfigPath : string = Path . convertToSlashes (
121+ path . relative ( this . _buildFolderPath , tsconfigFilePath )
122+ ) ;
123+ const tsconfigHash : Hash = createHash ( 'md5' ) ;
124+ tsconfigHash . update ( relativeTsconfigPath ) ;
125+ hashSuffix = tsconfigHash . digest ( 'base64url' ) . slice ( 0 , 8 ) ;
126+ } else {
127+ // Fallback to a default hash if configFilePath is not available
128+ hashSuffix = 'default' ;
94129 }
95- const hashSuffix : string = fileHash . digest ( 'base64' ) . replace ( / \+ / g, '-' ) . replace ( / \/ / g, '_' ) . slice ( 0 , 8 ) ;
96130
97131 const linterCacheVersion : string = await this . getCacheVersionAsync ( ) ;
98132 const linterCacheFilePath : string = path . resolve (
@@ -121,7 +155,9 @@ export abstract class LinterBase<TLintResult> {
121155 }
122156
123157 const cachedNoFailureFileVersions : Map < string , string > = new Map < string , string > (
124- linterCacheData ?. cacheVersion === linterCacheVersion ? linterCacheData . fileVersions : [ ]
158+ linterCacheData ?. cacheVersion === linterCacheVersion && linterCacheData ?. filesHash === filesHashString
159+ ? linterCacheData . fileVersions
160+ : [ ]
125161 ) ;
126162
127163 const newNoFailureFileVersions : Map < string , string > = new Map < string , string > ( ) ;
@@ -173,7 +209,8 @@ export abstract class LinterBase<TLintResult> {
173209
174210 const updatedTslintCacheData : ILinterCacheData = {
175211 cacheVersion : linterCacheVersion ,
176- fileVersions : Array . from ( newNoFailureFileVersions )
212+ fileVersions : Array . from ( newNoFailureFileVersions ) ,
213+ filesHash : filesHashString
177214 } ;
178215 await JsonFile . saveAsync ( updatedTslintCacheData , linterCacheFilePath , { ensureFolderExists : true } ) ;
179216
0 commit comments