From 274a7919c5a9b9967c33038d9002d58a0a6a608a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 6 Mar 2020 16:29:21 -0800 Subject: [PATCH 1/7] fix: unecessary program updates by removing timeout methods --- .../src/create-program/createWatchProgram.ts | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/packages/typescript-estree/src/create-program/createWatchProgram.ts b/packages/typescript-estree/src/create-program/createWatchProgram.ts index 143219998fa..e57cd22142b 100644 --- a/packages/typescript-estree/src/create-program/createWatchProgram.ts +++ b/packages/typescript-estree/src/create-program/createWatchProgram.ts @@ -310,23 +310,10 @@ function createWatchProgram( oldOnDirectoryStructureHostCreate(host); }; - /* - * The watch change callbacks TS provides us all have a 250ms delay before firing - * https://github.com/microsoft/TypeScript/blob/b845800bdfcc81c8c72e2ac6fdc2c1df0cdab6f9/src/compiler/watch.ts#L1013 - * - * We live in a synchronous world, so we can't wait for that. - * This is a bit of a hack, but it lets us immediately force updates when we detect a tsconfig or directory change - */ - const oldSetTimeout = watchCompilerHost.setTimeout; - watchCompilerHost.setTimeout = (cb, ms, ...args): unknown => { - if (ms === 250) { - cb(); - return null; - } - - return oldSetTimeout?.(cb, ms, ...args); - }; - + // Since we dont want to asynchronously update program disable timeout methods + // So any changes in the program will be delayed and updated when getProgram is called on watch + watchCompilerHost.setTimeout = undefined; + watchCompilerHost.clearTimeout = undefined; return ts.createWatchProgram(watchCompilerHost); } From aecb6bbee576f6884bc55f4397a9058a6f36bc49 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Fri, 6 Mar 2020 16:45:31 -0800 Subject: [PATCH 2/7] fix spelling for CI --- .../typescript-estree/src/create-program/createWatchProgram.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-estree/src/create-program/createWatchProgram.ts b/packages/typescript-estree/src/create-program/createWatchProgram.ts index e57cd22142b..a9b7c48cfaf 100644 --- a/packages/typescript-estree/src/create-program/createWatchProgram.ts +++ b/packages/typescript-estree/src/create-program/createWatchProgram.ts @@ -310,7 +310,7 @@ function createWatchProgram( oldOnDirectoryStructureHostCreate(host); }; - // Since we dont want to asynchronously update program disable timeout methods + // Since we don't want to asynchronously update program disable timeout methods // So any changes in the program will be delayed and updated when getProgram is called on watch watchCompilerHost.setTimeout = undefined; watchCompilerHost.clearTimeout = undefined; From 0510a22d6b2e7396a92edf274610d3d2b3f9696a Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 6 Mar 2020 18:24:39 -0800 Subject: [PATCH 3/7] fix: store hash of empty content file to avoid program update --- .../typescript-estree/src/create-program/createWatchProgram.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-estree/src/create-program/createWatchProgram.ts b/packages/typescript-estree/src/create-program/createWatchProgram.ts index a9b7c48cfaf..cd306f45713 100644 --- a/packages/typescript-estree/src/create-program/createWatchProgram.ts +++ b/packages/typescript-estree/src/create-program/createWatchProgram.ts @@ -250,7 +250,7 @@ function createWatchProgram( filePath === currentLintOperationState.filePath ? currentLintOperationState.code : oldReadFile(filePath, encoding); - if (fileContent) { + if (fileContent !== undefined) { parsedFilesSeenHash.set(filePath, createHash(fileContent)); } return fileContent; From 0351eead76eb4220778b3254fc7599223a9db403 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 9 Mar 2020 15:53:18 -0700 Subject: [PATCH 4/7] fix: instead of not using timeouts, callback them before getProgram --- .../src/create-program/createWatchProgram.ts | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/typescript-estree/src/create-program/createWatchProgram.ts b/packages/typescript-estree/src/create-program/createWatchProgram.ts index cd306f45713..836afbc2b5e 100644 --- a/packages/typescript-estree/src/create-program/createWatchProgram.ts +++ b/packages/typescript-estree/src/create-program/createWatchProgram.ts @@ -309,12 +309,27 @@ function createWatchProgram( ); oldOnDirectoryStructureHostCreate(host); }; + watchCompilerHost.trace = log; - // Since we don't want to asynchronously update program disable timeout methods + // Since we don't want to asynchronously update program we want to disable timeout methods // So any changes in the program will be delayed and updated when getProgram is called on watch - watchCompilerHost.setTimeout = undefined; - watchCompilerHost.clearTimeout = undefined; - return ts.createWatchProgram(watchCompilerHost); + // But because of https://github.com/microsoft/TypeScript/pull/37308 we cannot just set it to undefined + // instead save it and call before getProgram is called + let callback: (() => void) | undefined; + watchCompilerHost.setTimeout = (cb, _ms, ...args) => { + callback = cb.bind(/*this*/ undefined, ...args); + }; + watchCompilerHost.clearTimeout = () => { + callback = undefined; + }; + const watch = ts.createWatchProgram(watchCompilerHost); + const originalGetProgram = watch.getProgram; + watch.getProgram = () => { + if (callback) callback(); + callback = undefined; + return originalGetProgram.call(watch); + }; + return watch; } function hasTSConfigChanged(tsconfigPath: CanonicalPath): boolean { From 0b0760c8f4d8df4f578da33c0e6d23923f68a1bf Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Mon, 9 Mar 2020 16:17:43 -0700 Subject: [PATCH 5/7] fix: lint errors --- .../src/create-program/createWatchProgram.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/typescript-estree/src/create-program/createWatchProgram.ts b/packages/typescript-estree/src/create-program/createWatchProgram.ts index 836afbc2b5e..134e8355098 100644 --- a/packages/typescript-estree/src/create-program/createWatchProgram.ts +++ b/packages/typescript-estree/src/create-program/createWatchProgram.ts @@ -316,16 +316,19 @@ function createWatchProgram( // But because of https://github.com/microsoft/TypeScript/pull/37308 we cannot just set it to undefined // instead save it and call before getProgram is called let callback: (() => void) | undefined; - watchCompilerHost.setTimeout = (cb, _ms, ...args) => { + watchCompilerHost.setTimeout = (cb, _ms, ...args): unknown => { callback = cb.bind(/*this*/ undefined, ...args); + return callback; }; - watchCompilerHost.clearTimeout = () => { + watchCompilerHost.clearTimeout = (): void => { callback = undefined; }; const watch = ts.createWatchProgram(watchCompilerHost); const originalGetProgram = watch.getProgram; - watch.getProgram = () => { - if (callback) callback(); + watch.getProgram = (): ts.SemanticDiagnosticsBuilderProgram => { + if (callback) { + callback(); + } callback = undefined; return originalGetProgram.call(watch); }; From 2405fae845fec4f7da83aab48c95ed272b18015d Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 10 Mar 2020 16:12:11 -0700 Subject: [PATCH 6/7] fix: use Abstract Builder instead of semantic builder --- .../src/create-program/createWatchProgram.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/typescript-estree/src/create-program/createWatchProgram.ts b/packages/typescript-estree/src/create-program/createWatchProgram.ts index 134e8355098..c8548970692 100644 --- a/packages/typescript-estree/src/create-program/createWatchProgram.ts +++ b/packages/typescript-estree/src/create-program/createWatchProgram.ts @@ -18,7 +18,7 @@ const log = debug('typescript-eslint:typescript-estree:createWatchProgram'); */ const knownWatchProgramMap = new Map< CanonicalPath, - ts.WatchOfConfigFile + ts.WatchOfConfigFile >(); /** @@ -229,7 +229,7 @@ function getProgramsForProjects( function createWatchProgram( tsconfigPath: string, extra: Extra, -): ts.WatchOfConfigFile { +): ts.WatchOfConfigFile { log('Creating watch program for %s.', tsconfigPath); // create compiler host @@ -237,10 +237,10 @@ function createWatchProgram( tsconfigPath, createDefaultCompilerOptionsFromExtra(extra), ts.sys, - ts.createSemanticDiagnosticsBuilderProgram, + ts.createAbstractBuilder, diagnosticReporter, /*reportWatchStatus*/ () => {}, - ) as WatchCompilerHostOfConfigFile; + ) as WatchCompilerHostOfConfigFile; // ensure readFile reads the code being linted instead of the copy on disk const oldReadFile = watchCompilerHost.readFile; @@ -325,7 +325,7 @@ function createWatchProgram( }; const watch = ts.createWatchProgram(watchCompilerHost); const originalGetProgram = watch.getProgram; - watch.getProgram = (): ts.SemanticDiagnosticsBuilderProgram => { + watch.getProgram = (): ts.BuilderProgram => { if (callback) { callback(); } @@ -352,7 +352,7 @@ function hasTSConfigChanged(tsconfigPath: CanonicalPath): boolean { } function maybeInvalidateProgram( - existingWatch: ts.WatchOfConfigFile, + existingWatch: ts.WatchOfConfigFile, filePath: CanonicalPath, tsconfigPath: CanonicalPath, ): ts.Program | null { From d9a586746a9127b1c160d6d791c993a565603cee Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 10 Mar 2020 16:58:54 -0700 Subject: [PATCH 7/7] fix: handle typescript version for the no timeout fix --- .../src/create-program/createWatchProgram.ts | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/packages/typescript-estree/src/create-program/createWatchProgram.ts b/packages/typescript-estree/src/create-program/createWatchProgram.ts index c8548970692..682e1d7c731 100644 --- a/packages/typescript-estree/src/create-program/createWatchProgram.ts +++ b/packages/typescript-estree/src/create-program/createWatchProgram.ts @@ -1,5 +1,6 @@ import debug from 'debug'; import fs from 'fs'; +import semver from 'semver'; import * as ts from 'typescript'; import { Extra } from '../parser-options'; import { WatchCompilerHostOfConfigFile } from './WatchCompilerHostOfConfigFile'; @@ -226,6 +227,10 @@ function getProgramsForProjects( return results; } +const isRunningNoTimeoutFix = semver.satisfies(ts.version, '>=3.9.0-beta', { + includePrerelease: true, +}); + function createWatchProgram( tsconfigPath: string, extra: Extra, @@ -313,25 +318,33 @@ function createWatchProgram( // Since we don't want to asynchronously update program we want to disable timeout methods // So any changes in the program will be delayed and updated when getProgram is called on watch - // But because of https://github.com/microsoft/TypeScript/pull/37308 we cannot just set it to undefined - // instead save it and call before getProgram is called let callback: (() => void) | undefined; - watchCompilerHost.setTimeout = (cb, _ms, ...args): unknown => { - callback = cb.bind(/*this*/ undefined, ...args); - return callback; - }; - watchCompilerHost.clearTimeout = (): void => { - callback = undefined; - }; + if (isRunningNoTimeoutFix) { + watchCompilerHost.setTimeout = undefined; + watchCompilerHost.clearTimeout = undefined; + } else { + log('Running without timeout fix'); + // But because of https://github.com/microsoft/TypeScript/pull/37308 we cannot just set it to undefined + // instead save it and call before getProgram is called + watchCompilerHost.setTimeout = (cb, _ms, ...args): unknown => { + callback = cb.bind(/*this*/ undefined, ...args); + return callback; + }; + watchCompilerHost.clearTimeout = (): void => { + callback = undefined; + }; + } const watch = ts.createWatchProgram(watchCompilerHost); - const originalGetProgram = watch.getProgram; - watch.getProgram = (): ts.BuilderProgram => { - if (callback) { - callback(); - } - callback = undefined; - return originalGetProgram.call(watch); - }; + if (!isRunningNoTimeoutFix) { + const originalGetProgram = watch.getProgram; + watch.getProgram = (): ts.BuilderProgram => { + if (callback) { + callback(); + } + callback = undefined; + return originalGetProgram.call(watch); + }; + } return watch; }