Skip to content

Commit 2ccd66b

Browse files
authored
fix(typescript-estree): unnecessary program updates by removing timeout methods (typescript-eslint#1693)
1 parent 4ab3bf0 commit 2ccd66b

File tree

1 file changed

+43
-25
lines changed

1 file changed

+43
-25
lines changed

packages/typescript-estree/src/create-program/createWatchProgram.ts

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import debug from 'debug';
22
import fs from 'fs';
3+
import semver from 'semver';
34
import * as ts from 'typescript';
45
import { Extra } from '../parser-options';
56
import { WatchCompilerHostOfConfigFile } from './WatchCompilerHostOfConfigFile';
@@ -18,7 +19,7 @@ const log = debug('typescript-eslint:typescript-estree:createWatchProgram');
1819
*/
1920
const knownWatchProgramMap = new Map<
2021
CanonicalPath,
21-
ts.WatchOfConfigFile<ts.SemanticDiagnosticsBuilderProgram>
22+
ts.WatchOfConfigFile<ts.BuilderProgram>
2223
>();
2324

2425
/**
@@ -226,21 +227,25 @@ function getProgramsForProjects(
226227
return results;
227228
}
228229

230+
const isRunningNoTimeoutFix = semver.satisfies(ts.version, '>=3.9.0-beta', {
231+
includePrerelease: true,
232+
});
233+
229234
function createWatchProgram(
230235
tsconfigPath: string,
231236
extra: Extra,
232-
): ts.WatchOfConfigFile<ts.SemanticDiagnosticsBuilderProgram> {
237+
): ts.WatchOfConfigFile<ts.BuilderProgram> {
233238
log('Creating watch program for %s.', tsconfigPath);
234239

235240
// create compiler host
236241
const watchCompilerHost = ts.createWatchCompilerHost(
237242
tsconfigPath,
238243
createDefaultCompilerOptionsFromExtra(extra),
239244
ts.sys,
240-
ts.createSemanticDiagnosticsBuilderProgram,
245+
ts.createAbstractBuilder,
241246
diagnosticReporter,
242247
/*reportWatchStatus*/ () => {},
243-
) as WatchCompilerHostOfConfigFile<ts.SemanticDiagnosticsBuilderProgram>;
248+
) as WatchCompilerHostOfConfigFile<ts.BuilderProgram>;
244249

245250
// ensure readFile reads the code being linted instead of the copy on disk
246251
const oldReadFile = watchCompilerHost.readFile;
@@ -250,7 +255,7 @@ function createWatchProgram(
250255
filePath === currentLintOperationState.filePath
251256
? currentLintOperationState.code
252257
: oldReadFile(filePath, encoding);
253-
if (fileContent) {
258+
if (fileContent !== undefined) {
254259
parsedFilesSeenHash.set(filePath, createHash(fileContent));
255260
}
256261
return fileContent;
@@ -309,25 +314,38 @@ function createWatchProgram(
309314
);
310315
oldOnDirectoryStructureHostCreate(host);
311316
};
312-
313-
/*
314-
* The watch change callbacks TS provides us all have a 250ms delay before firing
315-
* https://github.com/microsoft/TypeScript/blob/b845800bdfcc81c8c72e2ac6fdc2c1df0cdab6f9/src/compiler/watch.ts#L1013
316-
*
317-
* We live in a synchronous world, so we can't wait for that.
318-
* This is a bit of a hack, but it lets us immediately force updates when we detect a tsconfig or directory change
319-
*/
320-
const oldSetTimeout = watchCompilerHost.setTimeout;
321-
watchCompilerHost.setTimeout = (cb, ms, ...args): unknown => {
322-
if (ms === 250) {
323-
cb();
324-
return null;
325-
}
326-
327-
return oldSetTimeout?.(cb, ms, ...args);
328-
};
329-
330-
return ts.createWatchProgram(watchCompilerHost);
317+
watchCompilerHost.trace = log;
318+
319+
// Since we don't want to asynchronously update program we want to disable timeout methods
320+
// So any changes in the program will be delayed and updated when getProgram is called on watch
321+
let callback: (() => void) | undefined;
322+
if (isRunningNoTimeoutFix) {
323+
watchCompilerHost.setTimeout = undefined;
324+
watchCompilerHost.clearTimeout = undefined;
325+
} else {
326+
log('Running without timeout fix');
327+
// But because of https://github.com/microsoft/TypeScript/pull/37308 we cannot just set it to undefined
328+
// instead save it and call before getProgram is called
329+
watchCompilerHost.setTimeout = (cb, _ms, ...args): unknown => {
330+
callback = cb.bind(/*this*/ undefined, ...args);
331+
return callback;
332+
};
333+
watchCompilerHost.clearTimeout = (): void => {
334+
callback = undefined;
335+
};
336+
}
337+
const watch = ts.createWatchProgram(watchCompilerHost);
338+
if (!isRunningNoTimeoutFix) {
339+
const originalGetProgram = watch.getProgram;
340+
watch.getProgram = (): ts.BuilderProgram => {
341+
if (callback) {
342+
callback();
343+
}
344+
callback = undefined;
345+
return originalGetProgram.call(watch);
346+
};
347+
}
348+
return watch;
331349
}
332350

333351
function hasTSConfigChanged(tsconfigPath: CanonicalPath): boolean {
@@ -347,7 +365,7 @@ function hasTSConfigChanged(tsconfigPath: CanonicalPath): boolean {
347365
}
348366

349367
function maybeInvalidateProgram(
350-
existingWatch: ts.WatchOfConfigFile<ts.SemanticDiagnosticsBuilderProgram>,
368+
existingWatch: ts.WatchOfConfigFile<ts.BuilderProgram>,
351369
filePath: CanonicalPath,
352370
tsconfigPath: CanonicalPath,
353371
): ts.Program | null {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy