1namespace ts { 2 export interface ReadBuildProgramHost { 3 useCaseSensitiveFileNames(): boolean; 4 getCurrentDirectory(): string; 5 readFile(fileName: string): string | undefined; 6 } 7 export function readBuilderProgram(compilerOptions: CompilerOptions, host: ReadBuildProgramHost) { 8 if (outFile(compilerOptions)) return undefined; 9 const buildInfoPath = getTsBuildInfoEmitOutputFilePath(compilerOptions); 10 if (!buildInfoPath) return undefined; 11 const content = host.readFile(buildInfoPath); 12 if (!content) return undefined; 13 const buildInfo = getBuildInfo(content); 14 if (buildInfo.version !== version) return undefined; 15 if (!buildInfo.program) return undefined; 16 return createBuildProgramUsingProgramBuildInfo(buildInfo.program, buildInfoPath, host); 17 } 18 19 export function createIncrementalCompilerHost(options: CompilerOptions, system = sys): CompilerHost { 20 const host = createCompilerHostWorker(options, /*setParentNodes*/ undefined, system); 21 host.createHash = maybeBind(system, system.createHash); 22 setGetSourceFileAsHashVersioned(host, system); 23 changeCompilerHostLikeToUseCache(host, fileName => toPath(fileName, host.getCurrentDirectory(), host.getCanonicalFileName)); 24 return host; 25 } 26 27 export interface IncrementalProgramOptions<T extends BuilderProgram> { 28 rootNames: readonly string[]; 29 options: CompilerOptions; 30 configFileParsingDiagnostics?: readonly Diagnostic[]; 31 projectReferences?: readonly ProjectReference[]; 32 host?: CompilerHost; 33 createProgram?: CreateProgram<T>; 34 } 35 36 export function createIncrementalProgram<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>({ 37 rootNames, options, configFileParsingDiagnostics, projectReferences, host, createProgram 38 }: IncrementalProgramOptions<T>): T { 39 host = host || createIncrementalCompilerHost(options); 40 createProgram = createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>; 41 const oldProgram = readBuilderProgram(options, host) as any as T; 42 return createProgram(rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences); 43 } 44 45 export type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions, errorCount?: number) => void; 46 /** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */ 47 export type CreateProgram<T extends BuilderProgram> = (rootNames: readonly string[] | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: readonly Diagnostic[], projectReferences?: readonly ProjectReference[] | undefined) => T; 48 49 /** Host that has watch functionality used in --watch mode */ 50 export interface WatchHost { 51 /** If provided, called with Diagnostic message that informs about change in watch status */ 52 onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions, errorCount?: number): void; 53 54 /** Used to watch changes in source files, missing files needed to update the program or config file */ 55 watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: CompilerOptions): FileWatcher; 56 /** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */ 57 watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: CompilerOptions): FileWatcher; 58 /** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */ 59 setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any; 60 /** If provided, will be used to reset existing delayed compilation */ 61 clearTimeout?(timeoutId: any): void; 62 } 63 export interface ProgramHost<T extends BuilderProgram> { 64 /** 65 * Used to create the program when need for program creation or recreation detected 66 */ 67 createProgram: CreateProgram<T>; 68 69 // Sub set of compiler host methods to read and generate new program 70 useCaseSensitiveFileNames(): boolean; 71 getNewLine(): string; 72 getCurrentDirectory(): string; 73 getDefaultLibFileName(options: CompilerOptions): string; 74 getDefaultLibLocation?(): string; 75 createHash?(data: string): string; 76 77 /** 78 * Use to check file presence for source files and 79 * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well 80 */ 81 fileExists(path: string): boolean; 82 /** 83 * Use to read file text for source files and 84 * if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well 85 */ 86 readFile(path: string, encoding?: string): string | undefined; 87 88 /** If provided, used for module resolution as well as to handle directory structure */ 89 directoryExists?(path: string): boolean; 90 /** If provided, used in resolutions as well as handling directory structure */ 91 getDirectories?(path: string): string[]; 92 /** If provided, used to cache and handle directory structure modifications */ 93 readDirectory?(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; 94 95 /** Symbol links resolution */ 96 realpath?(path: string): string; 97 /** If provided would be used to write log about compilation */ 98 trace?(s: string): void; 99 /** If provided is used to get the environment variable */ 100 getEnvironmentVariable?(name: string): string | undefined; 101 102 /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ 103 resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedModule | undefined)[]; 104 /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ 105 resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedTypeReferenceDirective | undefined)[]; 106 } 107 /** Internal interface used to wire emit through same host */ 108 109 /*@internal*/ 110 export interface ProgramHost<T extends BuilderProgram> { 111 // TODO: GH#18217 Optional methods are frequently asserted 112 createDirectory?(path: string): void; 113 writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void; 114 } 115 116 export interface WatchCompilerHost<T extends BuilderProgram> extends ProgramHost<T>, WatchHost { 117 /** Instead of using output d.ts file from project reference, use its source file */ 118 useSourceOfProjectReferenceRedirect?(): boolean; 119 120 /** If provided, callback to invoke after every new program creation */ 121 afterProgramCreate?(program: T): void; 122 } 123 124 /** 125 * Host to create watch with root files and options 126 */ 127 export interface WatchCompilerHostOfFilesAndCompilerOptions<T extends BuilderProgram> extends WatchCompilerHost<T> { 128 /** root files to use to generate program */ 129 rootFiles: string[]; 130 131 /** Compiler options */ 132 options: CompilerOptions; 133 134 watchOptions?: WatchOptions; 135 136 /** Project References */ 137 projectReferences?: readonly ProjectReference[]; 138 } 139 140 /** 141 * Host to create watch with config file 142 */ 143 export interface WatchCompilerHostOfConfigFile<T extends BuilderProgram> extends WatchCompilerHost<T>, ConfigFileDiagnosticsReporter { 144 /** Name of the config file to compile */ 145 configFileName: string; 146 147 /** Options to extend */ 148 optionsToExtend?: CompilerOptions; 149 150 watchOptionsToExtend?: WatchOptions; 151 152 extraFileExtensions?: readonly FileExtensionInfo[] 153 154 /** 155 * Used to generate source file names from the config file and its include, exclude, files rules 156 * and also to cache the directory stucture 157 */ 158 readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[]; 159 } 160 161 /** 162 * Host to create watch with config file that is already parsed (from tsc) 163 */ 164 /*@internal*/ 165 export interface WatchCompilerHostOfConfigFile<T extends BuilderProgram> extends WatchCompilerHost<T> { 166 configFileParsingResult?: ParsedCommandLine; 167 } 168 169 export interface Watch<T> { 170 /** Synchronize with host and get updated program */ 171 getProgram(): T; 172 /** Gets the existing program without synchronizing with changes on host */ 173 /*@internal*/ 174 getCurrentProgram(): T; 175 /** Closes the watch */ 176 close(): void; 177 } 178 179 /** 180 * Creates the watch what generates program using the config file 181 */ 182 export interface WatchOfConfigFile<T> extends Watch<T> { 183 } 184 185 /** 186 * Creates the watch that generates program using the root files and compiler options 187 */ 188 export interface WatchOfFilesAndCompilerOptions<T> extends Watch<T> { 189 /** Updates the root files in the program, only if this is not config file compilation */ 190 updateRootFileNames(fileNames: string[]): void; 191 } 192 193 /** 194 * Create the watch compiler host for either configFile or fileNames and its options 195 */ 196 export function createWatchCompilerHost<T extends BuilderProgram>(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, watchOptionsToExtend?: WatchOptions, extraFileExtensions?: readonly FileExtensionInfo[]): WatchCompilerHostOfConfigFile<T>; 197 export function createWatchCompilerHost<T extends BuilderProgram>(rootFiles: string[], options: CompilerOptions, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: readonly ProjectReference[], watchOptions?: WatchOptions): WatchCompilerHostOfFilesAndCompilerOptions<T>; 198 export function createWatchCompilerHost<T extends BuilderProgram>(rootFilesOrConfigFileName: string | string[], options: CompilerOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferencesOrWatchOptionsToExtend?: readonly ProjectReference[] | WatchOptions, watchOptionsOrExtraFileExtensions?: WatchOptions | readonly FileExtensionInfo[]): WatchCompilerHostOfFilesAndCompilerOptions<T> | WatchCompilerHostOfConfigFile<T> { 199 if (isArray(rootFilesOrConfigFileName)) { 200 return createWatchCompilerHostOfFilesAndCompilerOptions({ 201 rootFiles: rootFilesOrConfigFileName, 202 options: options!, 203 watchOptions: watchOptionsOrExtraFileExtensions as WatchOptions, 204 projectReferences: projectReferencesOrWatchOptionsToExtend as readonly ProjectReference[], 205 system, 206 createProgram, 207 reportDiagnostic, 208 reportWatchStatus, 209 }); 210 } 211 else { 212 return createWatchCompilerHostOfConfigFile({ 213 configFileName: rootFilesOrConfigFileName, 214 optionsToExtend: options, 215 watchOptionsToExtend: projectReferencesOrWatchOptionsToExtend as WatchOptions, 216 extraFileExtensions: watchOptionsOrExtraFileExtensions as readonly FileExtensionInfo[], 217 system, 218 createProgram, 219 reportDiagnostic, 220 reportWatchStatus, 221 }); 222 } 223 } 224 225 /** 226 * Creates the watch from the host for root files and compiler options 227 */ 228 export function createWatchProgram<T extends BuilderProgram>(host: WatchCompilerHostOfFilesAndCompilerOptions<T>): WatchOfFilesAndCompilerOptions<T>; 229 /** 230 * Creates the watch from the host for config file 231 */ 232 export function createWatchProgram<T extends BuilderProgram>(host: WatchCompilerHostOfConfigFile<T>): WatchOfConfigFile<T>; 233 export function createWatchProgram<T extends BuilderProgram>(host: WatchCompilerHostOfFilesAndCompilerOptions<T> & WatchCompilerHostOfConfigFile<T>): WatchOfFilesAndCompilerOptions<T> | WatchOfConfigFile<T> { 234 interface FilePresentOnHost { 235 version: string; 236 sourceFile: SourceFile; 237 fileWatcher: FileWatcher; 238 } 239 type FileMissingOnHost = false; 240 interface FilePresenceUnknownOnHost { 241 version: false; 242 fileWatcher?: FileWatcher; 243 } 244 type FileMayBePresentOnHost = FilePresentOnHost | FilePresenceUnknownOnHost; 245 type HostFileInfo = FilePresentOnHost | FileMissingOnHost | FilePresenceUnknownOnHost; 246 247 let builderProgram: T; 248 let reloadLevel: ConfigFileProgramReloadLevel; // level to indicate if the program needs to be reloaded from config file/just filenames etc 249 let extendedConfigFilesMap: ESMap<Path, FileWatcher>; // Map of file watchers for the extended config files 250 let missingFilesMap: ESMap<Path, FileWatcher>; // Map of file watchers for the missing files 251 let watchedWildcardDirectories: ESMap<string, WildcardDirectoryWatcher>; // map of watchers for the wild card directories in the config file 252 let timerToUpdateProgram: any; // timer callback to recompile the program 253 let timerToInvalidateFailedLookupResolutions: any; // timer callback to invalidate resolutions for changes in failed lookup locations 254 255 256 const sourceFilesCache = new Map<string, HostFileInfo>(); // Cache that stores the source file and version info 257 let missingFilePathsRequestedForRelease: Path[] | undefined; // These paths are held temporarily so that we can remove the entry from source file cache if the file is not tracked by missing files 258 let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations 259 260 const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames(); 261 const currentDirectory = host.getCurrentDirectory(); 262 const { configFileName, optionsToExtend: optionsToExtendForConfigFile = {}, watchOptionsToExtend, extraFileExtensions, createProgram } = host; 263 let { rootFiles: rootFileNames, options: compilerOptions, watchOptions, projectReferences } = host; 264 let wildcardDirectories: MapLike<WatchDirectoryFlags> | undefined; 265 let configFileParsingDiagnostics: Diagnostic[] | undefined; 266 let canConfigFileJsonReportNoInputFiles = false; 267 let hasChangedConfigFileParsingErrors = false; 268 269 const cachedDirectoryStructureHost = configFileName === undefined ? undefined : createCachedDirectoryStructureHost(host, currentDirectory, useCaseSensitiveFileNames); 270 const directoryStructureHost: DirectoryStructureHost = cachedDirectoryStructureHost || host; 271 const parseConfigFileHost = parseConfigHostFromCompilerHostLike(host, directoryStructureHost); 272 273 // From tsc we want to get already parsed result and hence check for rootFileNames 274 let newLine = updateNewLine(); 275 if (configFileName && host.configFileParsingResult) { 276 setConfigFileParsingResult(host.configFileParsingResult); 277 newLine = updateNewLine(); 278 } 279 reportWatchDiagnostic(Diagnostics.Starting_compilation_in_watch_mode); 280 if (configFileName && !host.configFileParsingResult) { 281 newLine = getNewLineCharacter(optionsToExtendForConfigFile, () => host.getNewLine()); 282 Debug.assert(!rootFileNames); 283 parseConfigFile(); 284 newLine = updateNewLine(); 285 } 286 287 const { watchFile, watchDirectory, writeLog } = createWatchFactory(host, compilerOptions); 288 const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); 289 290 writeLog(`Current directory: ${currentDirectory} CaseSensitiveFileNames: ${useCaseSensitiveFileNames}`); 291 let configFileWatcher: FileWatcher | undefined; 292 if (configFileName) { 293 configFileWatcher = watchFile(configFileName, scheduleProgramReload, PollingInterval.High, watchOptions, WatchType.ConfigFile); 294 } 295 296 const compilerHost = createCompilerHostFromProgramHost(host, () => compilerOptions, directoryStructureHost) as CompilerHost & ResolutionCacheHost; 297 setGetSourceFileAsHashVersioned(compilerHost, host); 298 // Members for CompilerHost 299 const getNewSourceFile = compilerHost.getSourceFile; 300 compilerHost.getSourceFile = (fileName, ...args) => getVersionedSourceFileByPath(fileName, toPath(fileName), ...args); 301 compilerHost.getSourceFileByPath = getVersionedSourceFileByPath; 302 compilerHost.getNewLine = () => newLine; 303 compilerHost.fileExists = fileExists; 304 compilerHost.onReleaseOldSourceFile = onReleaseOldSourceFile; 305 // Members for ResolutionCacheHost 306 compilerHost.toPath = toPath; 307 compilerHost.getCompilationSettings = () => compilerOptions; 308 compilerHost.useSourceOfProjectReferenceRedirect = maybeBind(host, host.useSourceOfProjectReferenceRedirect); 309 compilerHost.watchDirectoryOfFailedLookupLocation = (dir, cb, flags) => watchDirectory(dir, cb, flags, watchOptions, WatchType.FailedLookupLocations); 310 compilerHost.watchTypeRootsDirectory = (dir, cb, flags) => watchDirectory(dir, cb, flags, watchOptions, WatchType.TypeRoots); 311 compilerHost.getCachedDirectoryStructureHost = () => cachedDirectoryStructureHost; 312 compilerHost.scheduleInvalidateResolutionsOfFailedLookupLocations = scheduleInvalidateResolutionsOfFailedLookupLocations; 313 compilerHost.onInvalidatedResolution = scheduleProgramUpdate; 314 compilerHost.onChangedAutomaticTypeDirectiveNames = scheduleProgramUpdate; 315 compilerHost.fileIsOpen = returnFalse; 316 compilerHost.getCurrentProgram = getCurrentProgram; 317 compilerHost.writeLog = writeLog; 318 319 // Cache for the module resolution 320 const resolutionCache = createResolutionCache(compilerHost, 321 configFileName ? 322 getDirectoryPath(getNormalizedAbsolutePath(configFileName, currentDirectory)) : 323 currentDirectory, 324 /*logChangesWhenResolvingModule*/ false 325 ); 326 // Resolve module using host module resolution strategy if provided otherwise use resolution cache to resolve module names 327 compilerHost.resolveModuleNames = host.resolveModuleNames ? 328 ((...args) => host.resolveModuleNames!(...args)) : 329 ((moduleNames, containingFile, reusedNames, redirectedReference) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, redirectedReference)); 330 compilerHost.resolveTypeReferenceDirectives = host.resolveTypeReferenceDirectives ? 331 ((...args) => host.resolveTypeReferenceDirectives!(...args)) : 332 ((typeDirectiveNames, containingFile, redirectedReference) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile, redirectedReference)); 333 const userProvidedResolution = !!host.resolveModuleNames || !!host.resolveTypeReferenceDirectives; 334 335 builderProgram = readBuilderProgram(compilerOptions, compilerHost) as any as T; 336 synchronizeProgram(); 337 338 // Update the wild card directory watch 339 watchConfigFileWildCardDirectories(); 340 341 // Update extended config file watch 342 watchExtendedConfigFiles(); 343 344 return configFileName ? 345 { getCurrentProgram: getCurrentBuilderProgram, getProgram: updateProgram, close } : 346 { getCurrentProgram: getCurrentBuilderProgram, getProgram: updateProgram, updateRootFileNames, close }; 347 348 function close() { 349 clearInvalidateResolutionsOfFailedLookupLocations(); 350 resolutionCache.clear(); 351 clearMap(sourceFilesCache, value => { 352 if (value && value.fileWatcher) { 353 value.fileWatcher.close(); 354 value.fileWatcher = undefined; 355 } 356 }); 357 if (configFileWatcher) { 358 configFileWatcher.close(); 359 configFileWatcher = undefined; 360 } 361 if (extendedConfigFilesMap) { 362 clearMap(extendedConfigFilesMap, closeFileWatcher); 363 extendedConfigFilesMap = undefined!; 364 } 365 if (watchedWildcardDirectories) { 366 clearMap(watchedWildcardDirectories, closeFileWatcherOf); 367 watchedWildcardDirectories = undefined!; 368 } 369 if (missingFilesMap) { 370 clearMap(missingFilesMap, closeFileWatcher); 371 missingFilesMap = undefined!; 372 } 373 } 374 375 function getCurrentBuilderProgram() { 376 return builderProgram; 377 } 378 379 function getCurrentProgram() { 380 return builderProgram && builderProgram.getProgramOrUndefined(); 381 } 382 383 function synchronizeProgram() { 384 writeLog(`Synchronizing program`); 385 clearInvalidateResolutionsOfFailedLookupLocations(); 386 387 const program = getCurrentBuilderProgram(); 388 if (hasChangedCompilerOptions) { 389 newLine = updateNewLine(); 390 if (program && changesAffectModuleResolution(program.getCompilerOptions(), compilerOptions)) { 391 resolutionCache.clear(); 392 } 393 } 394 395 // All resolutions are invalid if user provided resolutions 396 const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(userProvidedResolution); 397 if (isProgramUptoDate(getCurrentProgram(), rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, projectReferences)) { 398 if (hasChangedConfigFileParsingErrors) { 399 builderProgram = createProgram(/*rootNames*/ undefined, /*options*/ undefined, compilerHost, builderProgram, configFileParsingDiagnostics, projectReferences); 400 hasChangedConfigFileParsingErrors = false; 401 } 402 } 403 else { 404 createNewProgram(hasInvalidatedResolution); 405 } 406 407 if (host.afterProgramCreate && program !== builderProgram) { 408 host.afterProgramCreate(builderProgram); 409 } 410 411 return builderProgram; 412 } 413 414 function createNewProgram(hasInvalidatedResolution: HasInvalidatedResolution) { 415 // Compile the program 416 writeLog("CreatingProgramWith::"); 417 writeLog(` roots: ${JSON.stringify(rootFileNames)}`); 418 writeLog(` options: ${JSON.stringify(compilerOptions)}`); 419 420 const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !getCurrentProgram(); 421 hasChangedCompilerOptions = false; 422 hasChangedConfigFileParsingErrors = false; 423 resolutionCache.startCachingPerDirectoryResolution(); 424 compilerHost.hasInvalidatedResolution = hasInvalidatedResolution; 425 compilerHost.hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames; 426 builderProgram = createProgram(rootFileNames, compilerOptions, compilerHost, builderProgram, configFileParsingDiagnostics, projectReferences); 427 resolutionCache.finishCachingPerDirectoryResolution(); 428 429 // Update watches 430 updateMissingFilePathsWatch(builderProgram.getProgram(), missingFilesMap || (missingFilesMap = new Map()), watchMissingFilePath); 431 if (needsUpdateInTypeRootWatch) { 432 resolutionCache.updateTypeRootsWatch(); 433 } 434 435 if (missingFilePathsRequestedForRelease) { 436 // These are the paths that program creater told us as not in use any more but were missing on the disk. 437 // We didnt remove the entry for them from sourceFiles cache so that we dont have to do File IO, 438 // if there is already watcher for it (for missing files) 439 // At this point our watches were updated, hence now we know that these paths are not tracked and need to be removed 440 // so that at later time we have correct result of their presence 441 for (const missingFilePath of missingFilePathsRequestedForRelease) { 442 if (!missingFilesMap.has(missingFilePath)) { 443 sourceFilesCache.delete(missingFilePath); 444 } 445 } 446 missingFilePathsRequestedForRelease = undefined; 447 } 448 } 449 450 function updateRootFileNames(files: string[]) { 451 Debug.assert(!configFileName, "Cannot update root file names with config file watch mode"); 452 rootFileNames = files; 453 scheduleProgramUpdate(); 454 } 455 456 function updateNewLine() { 457 return getNewLineCharacter(compilerOptions || optionsToExtendForConfigFile, () => host.getNewLine()); 458 } 459 460 function toPath(fileName: string) { 461 return ts.toPath(fileName, currentDirectory, getCanonicalFileName); 462 } 463 464 function isFileMissingOnHost(hostSourceFile: HostFileInfo | undefined): hostSourceFile is FileMissingOnHost { 465 return typeof hostSourceFile === "boolean"; 466 } 467 468 function isFilePresenceUnknownOnHost(hostSourceFile: FileMayBePresentOnHost): hostSourceFile is FilePresenceUnknownOnHost { 469 return typeof (hostSourceFile as FilePresenceUnknownOnHost).version === "boolean"; 470 } 471 472 function fileExists(fileName: string) { 473 const path = toPath(fileName); 474 // If file is missing on host from cache, we can definitely say file doesnt exist 475 // otherwise we need to ensure from the disk 476 if (isFileMissingOnHost(sourceFilesCache.get(path))) { 477 return false; 478 } 479 480 return directoryStructureHost.fileExists(fileName); 481 } 482 483 function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined { 484 const hostSourceFile = sourceFilesCache.get(path); 485 // No source file on the host 486 if (isFileMissingOnHost(hostSourceFile)) { 487 return undefined; 488 } 489 490 // Create new source file if requested or the versions dont match 491 if (hostSourceFile === undefined || shouldCreateNewSourceFile || isFilePresenceUnknownOnHost(hostSourceFile)) { 492 const sourceFile = getNewSourceFile(fileName, languageVersion, onError); 493 if (hostSourceFile) { 494 if (sourceFile) { 495 // Set the source file and create file watcher now that file was present on the disk 496 (hostSourceFile as FilePresentOnHost).sourceFile = sourceFile; 497 hostSourceFile.version = sourceFile.version; 498 if (!hostSourceFile.fileWatcher) { 499 hostSourceFile.fileWatcher = watchFilePath(path, fileName, onSourceFileChange, PollingInterval.Low, watchOptions, WatchType.SourceFile); 500 } 501 } 502 else { 503 // There is no source file on host any more, close the watch, missing file paths will track it 504 if (hostSourceFile.fileWatcher) { 505 hostSourceFile.fileWatcher.close(); 506 } 507 sourceFilesCache.set(path, false); 508 } 509 } 510 else { 511 if (sourceFile) { 512 const fileWatcher = watchFilePath(path, fileName, onSourceFileChange, PollingInterval.Low, watchOptions, WatchType.SourceFile); 513 sourceFilesCache.set(path, { sourceFile, version: sourceFile.version, fileWatcher }); 514 } 515 else { 516 sourceFilesCache.set(path, false); 517 } 518 } 519 return sourceFile; 520 } 521 return hostSourceFile.sourceFile; 522 } 523 524 function nextSourceFileVersion(path: Path) { 525 const hostSourceFile = sourceFilesCache.get(path); 526 if (hostSourceFile !== undefined) { 527 if (isFileMissingOnHost(hostSourceFile)) { 528 // The next version, lets set it as presence unknown file 529 sourceFilesCache.set(path, { version: false }); 530 } 531 else { 532 (hostSourceFile as FilePresenceUnknownOnHost).version = false; 533 } 534 } 535 } 536 537 function getSourceVersion(path: Path): string | undefined { 538 const hostSourceFile = sourceFilesCache.get(path); 539 return !hostSourceFile || !hostSourceFile.version ? undefined : hostSourceFile.version; 540 } 541 542 function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions, hasSourceFileByPath: boolean) { 543 const hostSourceFileInfo = sourceFilesCache.get(oldSourceFile.resolvedPath); 544 // If this is the source file thats in the cache and new program doesnt need it, 545 // remove the cached entry. 546 // Note we arent deleting entry if file became missing in new program or 547 // there was version update and new source file was created. 548 if (hostSourceFileInfo !== undefined) { 549 // record the missing file paths so they can be removed later if watchers arent tracking them 550 if (isFileMissingOnHost(hostSourceFileInfo)) { 551 (missingFilePathsRequestedForRelease || (missingFilePathsRequestedForRelease = [])).push(oldSourceFile.path); 552 } 553 else if ((hostSourceFileInfo as FilePresentOnHost).sourceFile === oldSourceFile) { 554 if (hostSourceFileInfo.fileWatcher) { 555 hostSourceFileInfo.fileWatcher.close(); 556 } 557 sourceFilesCache.delete(oldSourceFile.resolvedPath); 558 if (!hasSourceFileByPath) { 559 resolutionCache.removeResolutionsOfFile(oldSourceFile.path); 560 } 561 } 562 } 563 } 564 565 function reportWatchDiagnostic(message: DiagnosticMessage) { 566 if (host.onWatchStatusChange) { 567 host.onWatchStatusChange(createCompilerDiagnostic(message), newLine, compilerOptions || optionsToExtendForConfigFile); 568 } 569 } 570 571 function hasChangedAutomaticTypeDirectiveNames() { 572 return resolutionCache.hasChangedAutomaticTypeDirectiveNames(); 573 } 574 575 function clearInvalidateResolutionsOfFailedLookupLocations() { 576 if (!timerToInvalidateFailedLookupResolutions) return false; 577 host.clearTimeout!(timerToInvalidateFailedLookupResolutions); 578 timerToInvalidateFailedLookupResolutions = undefined; 579 return true; 580 } 581 582 function scheduleInvalidateResolutionsOfFailedLookupLocations() { 583 if (!host.setTimeout || !host.clearTimeout) { 584 return resolutionCache.invalidateResolutionsOfFailedLookupLocations(); 585 } 586 const pending = clearInvalidateResolutionsOfFailedLookupLocations(); 587 writeLog(`Scheduling invalidateFailedLookup${pending ? ", Cancelled earlier one" : ""}`); 588 timerToInvalidateFailedLookupResolutions = host.setTimeout(invalidateResolutionsOfFailedLookup, 250); 589 } 590 591 function invalidateResolutionsOfFailedLookup() { 592 timerToInvalidateFailedLookupResolutions = undefined; 593 if (resolutionCache.invalidateResolutionsOfFailedLookupLocations()) { 594 scheduleProgramUpdate(); 595 } 596 } 597 598 // Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch 599 // operations (such as saving all modified files in an editor) a chance to complete before we kick 600 // off a new compilation. 601 function scheduleProgramUpdate() { 602 if (!host.setTimeout || !host.clearTimeout) { 603 return; 604 } 605 606 if (timerToUpdateProgram) { 607 host.clearTimeout(timerToUpdateProgram); 608 } 609 writeLog("Scheduling update"); 610 timerToUpdateProgram = host.setTimeout(updateProgramWithWatchStatus, 250); 611 } 612 613 function scheduleProgramReload() { 614 Debug.assert(!!configFileName); 615 reloadLevel = ConfigFileProgramReloadLevel.Full; 616 scheduleProgramUpdate(); 617 } 618 619 function updateProgramWithWatchStatus() { 620 timerToUpdateProgram = undefined; 621 reportWatchDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation); 622 updateProgram(); 623 } 624 625 function updateProgram() { 626 switch (reloadLevel) { 627 case ConfigFileProgramReloadLevel.Partial: 628 perfLogger.logStartUpdateProgram("PartialConfigReload"); 629 reloadFileNamesFromConfigFile(); 630 break; 631 case ConfigFileProgramReloadLevel.Full: 632 perfLogger.logStartUpdateProgram("FullConfigReload"); 633 reloadConfigFile(); 634 break; 635 default: 636 perfLogger.logStartUpdateProgram("SynchronizeProgram"); 637 synchronizeProgram(); 638 break; 639 } 640 perfLogger.logStopUpdateProgram("Done"); 641 return getCurrentBuilderProgram(); 642 } 643 644 function reloadFileNamesFromConfigFile() { 645 writeLog("Reloading new file names and options"); 646 rootFileNames = getFileNamesFromConfigSpecs(compilerOptions.configFile!.configFileSpecs!, getNormalizedAbsolutePath(getDirectoryPath(configFileName), currentDirectory), compilerOptions, parseConfigFileHost, extraFileExtensions); 647 if (updateErrorForNoInputFiles(rootFileNames, getNormalizedAbsolutePath(configFileName, currentDirectory), compilerOptions.configFile!.configFileSpecs!, configFileParsingDiagnostics!, canConfigFileJsonReportNoInputFiles)) { 648 hasChangedConfigFileParsingErrors = true; 649 } 650 651 // Update the program 652 synchronizeProgram(); 653 } 654 655 function reloadConfigFile() { 656 writeLog(`Reloading config file: ${configFileName}`); 657 reloadLevel = ConfigFileProgramReloadLevel.None; 658 659 if (cachedDirectoryStructureHost) { 660 cachedDirectoryStructureHost.clearCache(); 661 } 662 parseConfigFile(); 663 hasChangedCompilerOptions = true; 664 synchronizeProgram(); 665 666 // Update the wild card directory watch 667 watchConfigFileWildCardDirectories(); 668 669 // Update extended config file watch 670 watchExtendedConfigFiles(); 671 } 672 673 function parseConfigFile() { 674 setConfigFileParsingResult(getParsedCommandLineOfConfigFile(configFileName, optionsToExtendForConfigFile, parseConfigFileHost, /*extendedConfigCache*/ undefined, watchOptionsToExtend, extraFileExtensions)!); // TODO: GH#18217 675 } 676 677 function setConfigFileParsingResult(configFileParseResult: ParsedCommandLine) { 678 rootFileNames = configFileParseResult.fileNames; 679 compilerOptions = configFileParseResult.options; 680 watchOptions = configFileParseResult.watchOptions; 681 projectReferences = configFileParseResult.projectReferences; 682 wildcardDirectories = configFileParseResult.wildcardDirectories; 683 configFileParsingDiagnostics = getConfigFileParsingDiagnostics(configFileParseResult).slice(); 684 canConfigFileJsonReportNoInputFiles = canJsonReportNoInputFiles(configFileParseResult.raw); 685 hasChangedConfigFileParsingErrors = true; 686 } 687 688 function watchFilePath( 689 path: Path, 690 file: string, 691 callback: (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void, 692 pollingInterval: PollingInterval, 693 options: WatchOptions | undefined, 694 watchType: WatchType 695 ): FileWatcher { 696 return watchFile(file, (fileName, eventKind) => callback(fileName, eventKind, path), pollingInterval, options, watchType); 697 } 698 699 function onSourceFileChange(fileName: string, eventKind: FileWatcherEventKind, path: Path) { 700 updateCachedSystemWithFile(fileName, path, eventKind); 701 702 // Update the source file cache 703 if (eventKind === FileWatcherEventKind.Deleted && sourceFilesCache.has(path)) { 704 resolutionCache.invalidateResolutionOfFile(path); 705 } 706 resolutionCache.removeResolutionsFromProjectReferenceRedirects(path); 707 nextSourceFileVersion(path); 708 709 // Update the program 710 scheduleProgramUpdate(); 711 } 712 713 function updateCachedSystemWithFile(fileName: string, path: Path, eventKind: FileWatcherEventKind) { 714 if (cachedDirectoryStructureHost) { 715 cachedDirectoryStructureHost.addOrDeleteFile(fileName, path, eventKind); 716 } 717 } 718 719 function watchMissingFilePath(missingFilePath: Path) { 720 return watchFilePath(missingFilePath, missingFilePath, onMissingFileChange, PollingInterval.Medium, watchOptions, WatchType.MissingFile); 721 } 722 723 function onMissingFileChange(fileName: string, eventKind: FileWatcherEventKind, missingFilePath: Path) { 724 updateCachedSystemWithFile(fileName, missingFilePath, eventKind); 725 726 if (eventKind === FileWatcherEventKind.Created && missingFilesMap.has(missingFilePath)) { 727 missingFilesMap.get(missingFilePath)!.close(); 728 missingFilesMap.delete(missingFilePath); 729 730 // Delete the entry in the source files cache so that new source file is created 731 nextSourceFileVersion(missingFilePath); 732 733 // When a missing file is created, we should update the graph. 734 scheduleProgramUpdate(); 735 } 736 } 737 738 function watchConfigFileWildCardDirectories() { 739 if (wildcardDirectories) { 740 updateWatchingWildcardDirectories( 741 watchedWildcardDirectories || (watchedWildcardDirectories = new Map()), 742 new Map(getEntries(wildcardDirectories)), 743 watchWildcardDirectory 744 ); 745 } 746 else if (watchedWildcardDirectories) { 747 clearMap(watchedWildcardDirectories, closeFileWatcherOf); 748 } 749 } 750 751 function watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags) { 752 return watchDirectory( 753 directory, 754 fileOrDirectory => { 755 Debug.assert(!!configFileName); 756 757 const fileOrDirectoryPath = toPath(fileOrDirectory); 758 759 // Since the file existence changed, update the sourceFiles cache 760 if (cachedDirectoryStructureHost) { 761 cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath); 762 } 763 nextSourceFileVersion(fileOrDirectoryPath); 764 765 if (isIgnoredFileFromWildCardWatching({ 766 watchedDirPath: toPath(directory), 767 fileOrDirectory, 768 fileOrDirectoryPath, 769 configFileName, 770 extraFileExtensions, 771 options: compilerOptions, 772 program: getCurrentBuilderProgram(), 773 currentDirectory, 774 useCaseSensitiveFileNames, 775 writeLog 776 })) return; 777 778 // Reload is pending, do the reload 779 if (reloadLevel !== ConfigFileProgramReloadLevel.Full) { 780 reloadLevel = ConfigFileProgramReloadLevel.Partial; 781 782 // Schedule Update the program 783 scheduleProgramUpdate(); 784 } 785 }, 786 flags, 787 watchOptions, 788 WatchType.WildcardDirectory 789 ); 790 } 791 792 function watchExtendedConfigFiles() { 793 // Update the extended config files watcher 794 mutateMap( 795 extendedConfigFilesMap ||= new Map(), 796 arrayToMap(compilerOptions.configFile?.extendedSourceFiles || emptyArray, toPath), 797 { 798 // Watch the extended config files 799 createNewValue: watchExtendedConfigFile, 800 // Config files that are no longer extended should no longer be watched. 801 onDeleteValue: closeFileWatcher 802 } 803 ); 804 } 805 806 function watchExtendedConfigFile(extendedConfigFile: Path) { 807 return watchFile(extendedConfigFile, scheduleProgramReload, PollingInterval.High, watchOptions, WatchType.ExtendedConfigFile); 808 } 809 } 810} 811