1
1
import debug from 'debug' ;
2
2
import fs from 'fs' ;
3
+ import semver from 'semver' ;
3
4
import * as ts from 'typescript' ;
4
5
import { Extra } from '../parser-options' ;
5
6
import { WatchCompilerHostOfConfigFile } from './WatchCompilerHostOfConfigFile' ;
@@ -18,7 +19,7 @@ const log = debug('typescript-eslint:typescript-estree:createWatchProgram');
18
19
*/
19
20
const knownWatchProgramMap = new Map <
20
21
CanonicalPath ,
21
- ts . WatchOfConfigFile < ts . SemanticDiagnosticsBuilderProgram >
22
+ ts . WatchOfConfigFile < ts . BuilderProgram >
22
23
> ( ) ;
23
24
24
25
/**
@@ -226,21 +227,25 @@ function getProgramsForProjects(
226
227
return results ;
227
228
}
228
229
230
+ const isRunningNoTimeoutFix = semver . satisfies ( ts . version , '>=3.9.0-beta' , {
231
+ includePrerelease : true ,
232
+ } ) ;
233
+
229
234
function createWatchProgram (
230
235
tsconfigPath : string ,
231
236
extra : Extra ,
232
- ) : ts . WatchOfConfigFile < ts . SemanticDiagnosticsBuilderProgram > {
237
+ ) : ts . WatchOfConfigFile < ts . BuilderProgram > {
233
238
log ( 'Creating watch program for %s.' , tsconfigPath ) ;
234
239
235
240
// create compiler host
236
241
const watchCompilerHost = ts . createWatchCompilerHost (
237
242
tsconfigPath ,
238
243
createDefaultCompilerOptionsFromExtra ( extra ) ,
239
244
ts . sys ,
240
- ts . createSemanticDiagnosticsBuilderProgram ,
245
+ ts . createAbstractBuilder ,
241
246
diagnosticReporter ,
242
247
/*reportWatchStatus*/ ( ) => { } ,
243
- ) as WatchCompilerHostOfConfigFile < ts . SemanticDiagnosticsBuilderProgram > ;
248
+ ) as WatchCompilerHostOfConfigFile < ts . BuilderProgram > ;
244
249
245
250
// ensure readFile reads the code being linted instead of the copy on disk
246
251
const oldReadFile = watchCompilerHost . readFile ;
@@ -250,7 +255,7 @@ function createWatchProgram(
250
255
filePath === currentLintOperationState . filePath
251
256
? currentLintOperationState . code
252
257
: oldReadFile ( filePath , encoding ) ;
253
- if ( fileContent ) {
258
+ if ( fileContent !== undefined ) {
254
259
parsedFilesSeenHash . set ( filePath , createHash ( fileContent ) ) ;
255
260
}
256
261
return fileContent ;
@@ -309,25 +314,38 @@ function createWatchProgram(
309
314
) ;
310
315
oldOnDirectoryStructureHostCreate ( host ) ;
311
316
} ;
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 ;
331
349
}
332
350
333
351
function hasTSConfigChanged ( tsconfigPath : CanonicalPath ) : boolean {
@@ -347,7 +365,7 @@ function hasTSConfigChanged(tsconfigPath: CanonicalPath): boolean {
347
365
}
348
366
349
367
function maybeInvalidateProgram (
350
- existingWatch : ts . WatchOfConfigFile < ts . SemanticDiagnosticsBuilderProgram > ,
368
+ existingWatch : ts . WatchOfConfigFile < ts . BuilderProgram > ,
351
369
filePath : CanonicalPath ,
352
370
tsconfigPath : CanonicalPath ,
353
371
) : ts . Program | null {
0 commit comments