Skip to content

Commit eff6731

Browse files
Copilotdmichon-msft
andcommitted
Stabilize hash suffix in linter cache file based on tsconfig path
Co-authored-by: dmichon-msft <[email protected]>
1 parent 2beae82 commit eff6731

File tree

2 files changed

+50
-10
lines changed

2 files changed

+50
-10
lines changed

heft-plugins/heft-lint-plugin/src/LintPlugin.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ interface ILintOptions {
3939
taskSession: IHeftTaskSession;
4040
heftConfiguration: HeftConfiguration;
4141
tsProgram: IExtendedProgram;
42+
tsconfigFilePath: string;
4243
fix?: boolean;
4344
sarifLogPath?: string;
4445
changedFiles?: ReadonlySet<IExtendedSourceFile>;
@@ -104,7 +105,8 @@ export default class LintPlugin implements IHeftTaskPlugin<ILintPluginOptions> {
104105
let inTypescriptPhase: boolean = false;
105106

106107
// Use the changed files hook to collect the files and programs from TypeScript
107-
let typescriptChangedFiles: [IExtendedProgram, ReadonlySet<IExtendedSourceFile>][] = [];
108+
// Also track the tsconfig path for cache file naming
109+
let typescriptChangedFiles: [IExtendedProgram, ReadonlySet<IExtendedSourceFile>, string][] = [];
108110
taskSession.requestAccessToPluginByName(
109111
TYPESCRIPT_PLUGIN_PACKAGE_NAME,
110112
TYPESCRIPT_PLUGIN_NAME,
@@ -114,9 +116,13 @@ export default class LintPlugin implements IHeftTaskPlugin<ILintPluginOptions> {
114116

115117
// Hook into the changed files hook to collect the changed files and their programs
116118
accessor.onChangedFilesHook.tap(PLUGIN_NAME, (changedFilesHookOptions: IChangedFilesHookOptions) => {
119+
// When using the TypeScript plugin, we need to determine the tsconfig path
120+
// The default tsconfig path is used when not explicitly specified
121+
const tsconfigPath: string = path.resolve(heftConfiguration.buildFolderPath, 'tsconfig.json');
117122
typescriptChangedFiles.push([
118123
changedFilesHookOptions.program as IExtendedProgram,
119-
changedFilesHookOptions.changedFiles as ReadonlySet<IExtendedSourceFile>
124+
changedFilesHookOptions.changedFiles as ReadonlySet<IExtendedSourceFile>,
125+
tsconfigPath
120126
]);
121127
});
122128
}
@@ -126,20 +132,22 @@ export default class LintPlugin implements IHeftTaskPlugin<ILintPluginOptions> {
126132
// If we are not in the typescript phase, we need to create a typescript program
127133
// from the tsconfig file
128134
if (!inTypescriptPhase) {
135+
const tsconfigPath: string = path.resolve(heftConfiguration.buildFolderPath, 'tsconfig.json');
129136
const tsProgram: IExtendedProgram = await this._createTypescriptProgramAsync(
130137
heftConfiguration,
131138
taskSession
132139
);
133-
typescriptChangedFiles.push([tsProgram, new Set(tsProgram.getSourceFiles())]);
140+
typescriptChangedFiles.push([tsProgram, new Set(tsProgram.getSourceFiles()), tsconfigPath]);
134141
}
135142

136143
// Run the linters to completion. Linters emit errors and warnings to the logger.
137-
for (const [tsProgram, changedFiles] of typescriptChangedFiles) {
144+
for (const [tsProgram, changedFiles, tsconfigFilePath] of typescriptChangedFiles) {
138145
try {
139146
await this._lintAsync({
140147
taskSession,
141148
heftConfiguration,
142149
tsProgram,
150+
tsconfigFilePath,
143151
changedFiles,
144152
fix,
145153
sarifLogPath
@@ -222,7 +230,8 @@ export default class LintPlugin implements IHeftTaskPlugin<ILintPluginOptions> {
222230
}
223231

224232
private async _lintAsync(options: ILintOptions): Promise<void> {
225-
const { taskSession, heftConfiguration, tsProgram, changedFiles, fix, sarifLogPath } = options;
233+
const { taskSession, heftConfiguration, tsProgram, tsconfigFilePath, changedFiles, fix, sarifLogPath } =
234+
options;
226235

227236
// Ensure that we have initialized. This promise is cached, so calling init
228237
// multiple times will only init once.
@@ -232,6 +241,7 @@ export default class LintPlugin implements IHeftTaskPlugin<ILintPluginOptions> {
232241
if (this._eslintConfigFilePath && this._eslintToolPath) {
233242
const eslintLinter: Eslint = await Eslint.initializeAsync({
234243
tsProgram,
244+
tsconfigFilePath,
235245
fix,
236246
sarifLogPath,
237247
scopedLogger: taskSession.logger,
@@ -246,6 +256,7 @@ export default class LintPlugin implements IHeftTaskPlugin<ILintPluginOptions> {
246256
if (this._tslintConfigFilePath && this._tslintToolPath) {
247257
const tslintLinter: Tslint = await Tslint.initializeAsync({
248258
tsProgram,
259+
tsconfigFilePath,
249260
fix,
250261
scopedLogger: taskSession.logger,
251262
linterToolPath: this._tslintToolPath,

heft-plugins/heft-lint-plugin/src/LinterBase.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface ILinterBaseOptions {
2121
linterToolPath: string;
2222
linterConfigFilePath: string;
2323
tsProgram: IExtendedProgram;
24+
tsconfigFilePath: string;
2425
fix?: boolean;
2526
sarifLogPath?: string;
2627
}
@@ -51,6 +52,12 @@ interface ILinterCacheData {
5152
* each array item is the file's path and the second element is the file's hash.
5253
*/
5354
fileVersions: [string, string][];
55+
56+
/**
57+
* A hash of the list of filenames that were linted. This is used to verify that
58+
* the cache was run with the same files.
59+
*/
60+
filesHash?: string;
5461
}
5562

5663
export abstract class LinterBase<TLintResult> {
@@ -59,6 +66,7 @@ export abstract class LinterBase<TLintResult> {
5966
protected readonly _buildFolderPath: string;
6067
protected readonly _buildMetadataFolderPath: string;
6168
protected readonly _linterConfigFilePath: string;
69+
protected readonly _tsconfigFilePath: string;
6270
protected readonly _fix: boolean;
6371

6472
protected _fixesPossible: boolean = false;
@@ -71,6 +79,7 @@ export abstract class LinterBase<TLintResult> {
7179
this._buildFolderPath = options.buildFolderPath;
7280
this._buildMetadataFolderPath = options.buildMetadataFolderPath;
7381
this._linterConfigFilePath = options.linterConfigFilePath;
82+
this._tsconfigFilePath = options.tsconfigFilePath;
7483
this._linterName = linterName;
7584
this._fix = options.fix || false;
7685
}
@@ -85,14 +94,31 @@ export abstract class LinterBase<TLintResult> {
8594

8695
const relativePaths: Map<string, string> = new Map();
8796

88-
const fileHash: Hash = createHash('md5');
97+
// Calculate the hash of the list of filenames for verification purposes
98+
const filesHash: Hash = createHash('md5');
8999
for (const file of options.typeScriptFilenames) {
90100
// Need to use relative paths to ensure portability.
91101
const relative: string = Path.convertToSlashes(path.relative(commonDirectory, file));
92102
relativePaths.set(file, relative);
93-
fileHash.update(relative);
103+
filesHash.update(relative);
94104
}
95-
const hashSuffix: string = fileHash.digest('base64').replace(/\+/g, '-').replace(/\//g, '_').slice(0, 8);
105+
const filesHashString: string = filesHash
106+
.digest('base64')
107+
.replace(/\+/g, '-')
108+
.replace(/\//g, '_')
109+
.slice(0, 8);
110+
111+
// Calculate the hash suffix based on the project-relative path of the tsconfig file
112+
const relativeTsconfigPath: string = Path.convertToSlashes(
113+
path.relative(this._buildFolderPath, this._tsconfigFilePath)
114+
);
115+
const tsconfigHash: Hash = createHash('md5');
116+
tsconfigHash.update(relativeTsconfigPath);
117+
const hashSuffix: string = tsconfigHash
118+
.digest('base64')
119+
.replace(/\+/g, '-')
120+
.replace(/\//g, '_')
121+
.slice(0, 8);
96122

97123
const linterCacheVersion: string = await this.getCacheVersionAsync();
98124
const linterCacheFilePath: string = path.resolve(
@@ -121,7 +147,9 @@ export abstract class LinterBase<TLintResult> {
121147
}
122148

123149
const cachedNoFailureFileVersions: Map<string, string> = new Map<string, string>(
124-
linterCacheData?.cacheVersion === linterCacheVersion ? linterCacheData.fileVersions : []
150+
linterCacheData?.cacheVersion === linterCacheVersion && linterCacheData?.filesHash === filesHashString
151+
? linterCacheData.fileVersions
152+
: []
125153
);
126154

127155
const newNoFailureFileVersions: Map<string, string> = new Map<string, string>();
@@ -172,7 +200,8 @@ export abstract class LinterBase<TLintResult> {
172200

173201
const updatedTslintCacheData: ILinterCacheData = {
174202
cacheVersion: linterCacheVersion,
175-
fileVersions: Array.from(newNoFailureFileVersions)
203+
fileVersions: Array.from(newNoFailureFileVersions),
204+
filesHash: filesHashString
176205
};
177206
await JsonFile.saveAsync(updatedTslintCacheData, linterCacheFilePath, { ensureFolderExists: true });
178207

0 commit comments

Comments
 (0)