Skip to content

Commit ba0baea

Browse files
Copilotdmichon-msft
andcommitted
Address feedback: use program.getCompilerOptions().configFilePath, base64url encoding, and sort files
Co-authored-by: dmichon-msft <[email protected]>
1 parent eff6731 commit ba0baea

File tree

2 files changed

+33
-36
lines changed

2 files changed

+33
-36
lines changed

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

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

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

117115
// Hook into the changed files hook to collect the changed files and their programs
118116
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');
122117
typescriptChangedFiles.push([
123118
changedFilesHookOptions.program as IExtendedProgram,
124-
changedFilesHookOptions.changedFiles as ReadonlySet<IExtendedSourceFile>,
125-
tsconfigPath
119+
changedFilesHookOptions.changedFiles as ReadonlySet<IExtendedSourceFile>
126120
]);
127121
});
128122
}
@@ -132,22 +126,20 @@ export default class LintPlugin implements IHeftTaskPlugin<ILintPluginOptions> {
132126
// If we are not in the typescript phase, we need to create a typescript program
133127
// from the tsconfig file
134128
if (!inTypescriptPhase) {
135-
const tsconfigPath: string = path.resolve(heftConfiguration.buildFolderPath, 'tsconfig.json');
136129
const tsProgram: IExtendedProgram = await this._createTypescriptProgramAsync(
137130
heftConfiguration,
138131
taskSession
139132
);
140-
typescriptChangedFiles.push([tsProgram, new Set(tsProgram.getSourceFiles()), tsconfigPath]);
133+
typescriptChangedFiles.push([tsProgram, new Set(tsProgram.getSourceFiles())]);
141134
}
142135

143136
// Run the linters to completion. Linters emit errors and warnings to the logger.
144-
for (const [tsProgram, changedFiles, tsconfigFilePath] of typescriptChangedFiles) {
137+
for (const [tsProgram, changedFiles] of typescriptChangedFiles) {
145138
try {
146139
await this._lintAsync({
147140
taskSession,
148141
heftConfiguration,
149142
tsProgram,
150-
tsconfigFilePath,
151143
changedFiles,
152144
fix,
153145
sarifLogPath
@@ -230,8 +222,7 @@ export default class LintPlugin implements IHeftTaskPlugin<ILintPluginOptions> {
230222
}
231223

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

236227
// Ensure that we have initialized. This promise is cached, so calling init
237228
// multiple times will only init once.
@@ -241,7 +232,6 @@ export default class LintPlugin implements IHeftTaskPlugin<ILintPluginOptions> {
241232
if (this._eslintConfigFilePath && this._eslintToolPath) {
242233
const eslintLinter: Eslint = await Eslint.initializeAsync({
243234
tsProgram,
244-
tsconfigFilePath,
245235
fix,
246236
sarifLogPath,
247237
scopedLogger: taskSession.logger,
@@ -256,7 +246,6 @@ export default class LintPlugin implements IHeftTaskPlugin<ILintPluginOptions> {
256246
if (this._tslintConfigFilePath && this._tslintToolPath) {
257247
const tslintLinter: Tslint = await Tslint.initializeAsync({
258248
tsProgram,
259-
tsconfigFilePath,
260249
fix,
261250
scopedLogger: taskSession.logger,
262251
linterToolPath: this._tslintToolPath,

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

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import * as path from 'node:path';
55
import { performance } from 'node:perf_hooks';
66
import { createHash, type Hash } from 'node:crypto';
77

8+
import type * as TTypescript from 'typescript';
9+
810
import { FileSystem, JsonFile, Path } from '@rushstack/node-core-library';
911
import type { ITerminal } from '@rushstack/terminal';
1012
import type { IScopedLogger } from '@rushstack/heft';
@@ -21,7 +23,6 @@ export interface ILinterBaseOptions {
2123
linterToolPath: string;
2224
linterConfigFilePath: string;
2325
tsProgram: IExtendedProgram;
24-
tsconfigFilePath: string;
2526
fix?: boolean;
2627
sarifLogPath?: string;
2728
}
@@ -66,7 +67,6 @@ export abstract class LinterBase<TLintResult> {
6667
protected readonly _buildFolderPath: string;
6768
protected readonly _buildMetadataFolderPath: string;
6869
protected readonly _linterConfigFilePath: string;
69-
protected readonly _tsconfigFilePath: string;
7070
protected readonly _fix: boolean;
7171

7272
protected _fixesPossible: boolean = false;
@@ -79,7 +79,6 @@ export abstract class LinterBase<TLintResult> {
7979
this._buildFolderPath = options.buildFolderPath;
8080
this._buildMetadataFolderPath = options.buildMetadataFolderPath;
8181
this._linterConfigFilePath = options.linterConfigFilePath;
82-
this._tsconfigFilePath = options.tsconfigFilePath;
8382
this._linterName = linterName;
8483
this._fix = options.fix || false;
8584
}
@@ -94,31 +93,40 @@ export abstract class LinterBase<TLintResult> {
9493

9594
const relativePaths: Map<string, string> = new Map();
9695

97-
// Calculate the hash of the list of filenames for verification purposes
98-
const filesHash: Hash = createHash('md5');
96+
// Collect and sort file paths for stable hashing
97+
const relativePathsArray: string[] = [];
9998
for (const file of options.typeScriptFilenames) {
10099
// Need to use relative paths to ensure portability.
101100
const relative: string = Path.convertToSlashes(path.relative(commonDirectory, file));
102101
relativePaths.set(file, 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) {
103109
filesHash.update(relative);
104110
}
105-
const filesHashString: string = filesHash
106-
.digest('base64')
107-
.replace(/\+/g, '-')
108-
.replace(/\//g, '_')
109-
.slice(0, 8);
111+
const filesHashString: string = filesHash.digest('base64url');
110112

111113
// 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);
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';
129+
}
122130

123131
const linterCacheVersion: string = await this.getCacheVersionAsync();
124132
const linterCacheFilePath: string = path.resolve(

0 commit comments

Comments
 (0)