TypeScript/src/compiler/watchPublic.ts

1038 lines
56 KiB
TypeScript
Raw Permalink Normal View History

namespace ts {
export interface ReadBuildProgramHost {
useCaseSensitiveFileNames(): boolean;
getCurrentDirectory(): string;
readFile(fileName: string): string | undefined;
}
export function readBuilderProgram(compilerOptions: CompilerOptions, host: ReadBuildProgramHost) {
if (outFile(compilerOptions)) return undefined;
const buildInfoPath = getTsBuildInfoEmitOutputFilePath(compilerOptions);
if (!buildInfoPath) return undefined;
const content = host.readFile(buildInfoPath);
if (!content) return undefined;
const buildInfo = getBuildInfo(content);
if (buildInfo.version !== version) return undefined;
if (!buildInfo.program) return undefined;
return createBuildProgramUsingProgramBuildInfo(buildInfo.program, buildInfoPath, host);
}
export function createIncrementalCompilerHost(options: CompilerOptions, system = sys): CompilerHost {
const host = createCompilerHostWorker(options, /*setParentNodes*/ undefined, system);
host.createHash = maybeBind(system, system.createHash);
host.disableUseFileVersionAsSignature = system.disableUseFileVersionAsSignature;
setGetSourceFileAsHashVersioned(host, system);
changeCompilerHostLikeToUseCache(host, fileName => toPath(fileName, host.getCurrentDirectory(), host.getCanonicalFileName));
return host;
}
export interface IncrementalProgramOptions<T extends BuilderProgram> {
rootNames: readonly string[];
options: CompilerOptions;
configFileParsingDiagnostics?: readonly Diagnostic[];
projectReferences?: readonly ProjectReference[];
host?: CompilerHost;
createProgram?: CreateProgram<T>;
}
export function createIncrementalProgram<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>({
rootNames, options, configFileParsingDiagnostics, projectReferences, host, createProgram
}: IncrementalProgramOptions<T>): T {
host = host || createIncrementalCompilerHost(options);
createProgram = createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>;
const oldProgram = readBuilderProgram(options, host) as any as T;
return createProgram(rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences);
}
export type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions, errorCount?: number) => void;
/** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */
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;
/** Host that has watch functionality used in --watch mode */
export interface WatchHost {
/** If provided, called with Diagnostic message that informs about change in watch status */
onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions, errorCount?: number): void;
/** Used to watch changes in source files, missing files needed to update the program or config file */
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: CompilerOptions): FileWatcher;
/** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: CompilerOptions): FileWatcher;
/** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */
setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
/** If provided, will be used to reset existing delayed compilation */
clearTimeout?(timeoutId: any): void;
}
export interface ProgramHost<T extends BuilderProgram> {
/**
* Used to create the program when need for program creation or recreation detected
*/
createProgram: CreateProgram<T>;
// Sub set of compiler host methods to read and generate new program
useCaseSensitiveFileNames(): boolean;
getNewLine(): string;
getCurrentDirectory(): string;
getDefaultLibFileName(options: CompilerOptions): string;
getDefaultLibLocation?(): string;
createHash?(data: string): string;
/**
* Use to check file presence for source files and
* if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well
*/
fileExists(path: string): boolean;
/**
* Use to read file text for source files and
* if resolveModuleNames is not provided (complier is in charge of module resolution) then module files as well
*/
readFile(path: string, encoding?: string): string | undefined;
/** If provided, used for module resolution as well as to handle directory structure */
directoryExists?(path: string): boolean;
/** If provided, used in resolutions as well as handling directory structure */
getDirectories?(path: string): string[];
/** If provided, used to cache and handle directory structure modifications */
readDirectory?(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[];
/** Symbol links resolution */
realpath?(path: string): string;
/** If provided would be used to write log about compilation */
trace?(s: string): void;
/** If provided is used to get the environment variable */
getEnvironmentVariable?(name: string): string | undefined;
/** If provided, used to resolve the module names, otherwise typescript's default module resolution */
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[];
/** If provided, used to resolve type reference directives, otherwise typescript's default resolution */
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedTypeReferenceDirective | undefined)[];
}
/** Internal interface used to wire emit through same host */
/*@internal*/
export interface ProgramHost<T extends BuilderProgram> {
// TODO: GH#18217 Optional methods are frequently asserted
createDirectory?(path: string): void;
writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void;
// For testing
disableUseFileVersionAsSignature?: boolean;
}
export interface WatchCompilerHost<T extends BuilderProgram> extends ProgramHost<T>, WatchHost {
/** Instead of using output d.ts file from project reference, use its source file */
useSourceOfProjectReferenceRedirect?(): boolean;
/** If provided, use this method to get parsed command lines for referenced projects */
getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined;
/** If provided, callback to invoke after every new program creation */
afterProgramCreate?(program: T): void;
}
/**
* Host to create watch with root files and options
*/
export interface WatchCompilerHostOfFilesAndCompilerOptions<T extends BuilderProgram> extends WatchCompilerHost<T> {
/** root files to use to generate program */
rootFiles: string[];
/** Compiler options */
options: CompilerOptions;
watchOptions?: WatchOptions;
/** Project References */
projectReferences?: readonly ProjectReference[];
}
/**
* Host to create watch with config file
*/
export interface WatchCompilerHostOfConfigFile<T extends BuilderProgram> extends WatchCompilerHost<T>, ConfigFileDiagnosticsReporter {
/** Name of the config file to compile */
configFileName: string;
/** Options to extend */
optionsToExtend?: CompilerOptions;
watchOptionsToExtend?: WatchOptions;
extraFileExtensions?: readonly FileExtensionInfo[]
/**
* Used to generate source file names from the config file and its include, exclude, files rules
* and also to cache the directory stucture
*/
readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[];
}
/**
* Host to create watch with config file that is already parsed (from tsc)
*/
/*@internal*/
export interface WatchCompilerHostOfConfigFile<T extends BuilderProgram> extends WatchCompilerHost<T> {
configFileParsingResult?: ParsedCommandLine;
extendedConfigCache?: Map<ExtendedConfigCacheEntry>;
}
export interface Watch<T> {
/** Synchronize with host and get updated program */
getProgram(): T;
/** Gets the existing program without synchronizing with changes on host */
/*@internal*/
getCurrentProgram(): T;
/** Closes the watch */
close(): void;
}
/**
* Creates the watch what generates program using the config file
*/
export interface WatchOfConfigFile<T> extends Watch<T> {
}
/**
* Creates the watch that generates program using the root files and compiler options
*/
export interface WatchOfFilesAndCompilerOptions<T> extends Watch<T> {
/** Updates the root files in the program, only if this is not config file compilation */
updateRootFileNames(fileNames: string[]): void;
}
/**
* Create the watch compiler host for either configFile or fileNames and its options
*/
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>;
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>;
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> {
if (isArray(rootFilesOrConfigFileName)) {
return createWatchCompilerHostOfFilesAndCompilerOptions({
rootFiles: rootFilesOrConfigFileName,
options: options!,
watchOptions: watchOptionsOrExtraFileExtensions as WatchOptions,
projectReferences: projectReferencesOrWatchOptionsToExtend as readonly ProjectReference[],
system,
createProgram,
reportDiagnostic,
reportWatchStatus,
});
}
else {
return createWatchCompilerHostOfConfigFile({
configFileName: rootFilesOrConfigFileName,
optionsToExtend: options,
watchOptionsToExtend: projectReferencesOrWatchOptionsToExtend as WatchOptions,
extraFileExtensions: watchOptionsOrExtraFileExtensions as readonly FileExtensionInfo[],
system,
createProgram,
reportDiagnostic,
reportWatchStatus,
});
}
}
interface ParsedConfig {
/** ParsedCommandLine for the config file if present */
parsedCommandLine: ParsedCommandLine | undefined;
/** File watcher of the config file */
watcher?: FileWatcher;
/** Wild card directories watched from this config file */
watchedDirectories?: Map<WildcardDirectoryWatcher>;
/** Reload to be done for this config file */
reloadLevel?: ConfigFileProgramReloadLevel.Partial | ConfigFileProgramReloadLevel.Full;
}
/**
* Creates the watch from the host for root files and compiler options
*/
export function createWatchProgram<T extends BuilderProgram>(host: WatchCompilerHostOfFilesAndCompilerOptions<T>): WatchOfFilesAndCompilerOptions<T>;
/**
* Creates the watch from the host for config file
*/
export function createWatchProgram<T extends BuilderProgram>(host: WatchCompilerHostOfConfigFile<T>): WatchOfConfigFile<T>;
export function createWatchProgram<T extends BuilderProgram>(host: WatchCompilerHostOfFilesAndCompilerOptions<T> & WatchCompilerHostOfConfigFile<T>): WatchOfFilesAndCompilerOptions<T> | WatchOfConfigFile<T> {
interface FilePresentOnHost {
version: string;
sourceFile: SourceFile;
fileWatcher: FileWatcher;
}
type FileMissingOnHost = false;
interface FilePresenceUnknownOnHost {
version: false;
fileWatcher?: FileWatcher;
}
type FileMayBePresentOnHost = FilePresentOnHost | FilePresenceUnknownOnHost;
type HostFileInfo = FilePresentOnHost | FileMissingOnHost | FilePresenceUnknownOnHost;
let builderProgram: T;
let reloadLevel: ConfigFileProgramReloadLevel; // level to indicate if the program needs to be reloaded from config file/just filenames etc
let missingFilesMap: ESMap<Path, FileWatcher>; // Map of file watchers for the missing files
let packageJsonMap: ESMap<Path, FileWatcher>; // map of watchers for package json files used in module resolution
let watchedWildcardDirectories: ESMap<string, WildcardDirectoryWatcher>; // map of watchers for the wild card directories in the config file
let timerToUpdateProgram: any; // timer callback to recompile the program
let timerToInvalidateFailedLookupResolutions: any; // timer callback to invalidate resolutions for changes in failed lookup locations
let parsedConfigs: ESMap<Path, ParsedConfig> | undefined; // Parsed commandline and watching cached for referenced projects
let sharedExtendedConfigFileWatchers: ESMap<Path, SharedExtendedConfigFileWatcher<Path>>; // Map of file watchers for extended files, shared between different referenced projects
let extendedConfigCache = host.extendedConfigCache; // Cache for extended config evaluation
let changesAffectResolution = false; // Flag for indicating non-config changes affect module resolution
const sourceFilesCache = new Map<string, HostFileInfo>(); // Cache that stores the source file and version info
2020-12-01 22:46:41 +01:00
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
let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations
const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames();
const currentDirectory = host.getCurrentDirectory();
const { configFileName, optionsToExtend: optionsToExtendForConfigFile = {}, watchOptionsToExtend, extraFileExtensions, createProgram } = host;
let { rootFiles: rootFileNames, options: compilerOptions, watchOptions, projectReferences } = host;
let wildcardDirectories: MapLike<WatchDirectoryFlags> | undefined;
let configFileParsingDiagnostics: Diagnostic[] | undefined;
let canConfigFileJsonReportNoInputFiles = false;
let hasChangedConfigFileParsingErrors = false;
const cachedDirectoryStructureHost = configFileName === undefined ? undefined : createCachedDirectoryStructureHost(host, currentDirectory, useCaseSensitiveFileNames);
const directoryStructureHost: DirectoryStructureHost = cachedDirectoryStructureHost || host;
const parseConfigFileHost = parseConfigHostFromCompilerHostLike(host, directoryStructureHost);
// From tsc we want to get already parsed result and hence check for rootFileNames
let newLine = updateNewLine();
if (configFileName && host.configFileParsingResult) {
setConfigFileParsingResult(host.configFileParsingResult);
newLine = updateNewLine();
}
reportWatchDiagnostic(Diagnostics.Starting_compilation_in_watch_mode);
if (configFileName && !host.configFileParsingResult) {
newLine = getNewLineCharacter(optionsToExtendForConfigFile, () => host.getNewLine());
Debug.assert(!rootFileNames);
parseConfigFile();
newLine = updateNewLine();
}
const { watchFile, watchDirectory, writeLog } = createWatchFactory(host, compilerOptions);
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
writeLog(`Current directory: ${currentDirectory} CaseSensitiveFileNames: ${useCaseSensitiveFileNames}`);
let configFileWatcher: FileWatcher | undefined;
if (configFileName) {
configFileWatcher = watchFile(configFileName, scheduleProgramReload, PollingInterval.High, watchOptions, WatchType.ConfigFile);
}
const compilerHost = createCompilerHostFromProgramHost(host, () => compilerOptions, directoryStructureHost) as CompilerHost & ResolutionCacheHost;
setGetSourceFileAsHashVersioned(compilerHost, host);
// Members for CompilerHost
const getNewSourceFile = compilerHost.getSourceFile;
compilerHost.getSourceFile = (fileName, ...args) => getVersionedSourceFileByPath(fileName, toPath(fileName), ...args);
compilerHost.getSourceFileByPath = getVersionedSourceFileByPath;
compilerHost.getNewLine = () => newLine;
compilerHost.fileExists = fileExists;
compilerHost.onReleaseOldSourceFile = onReleaseOldSourceFile;
compilerHost.onReleaseParsedCommandLine = onReleaseParsedCommandLine;
// Members for ResolutionCacheHost
compilerHost.toPath = toPath;
compilerHost.getCompilationSettings = () => compilerOptions;
compilerHost.useSourceOfProjectReferenceRedirect = maybeBind(host, host.useSourceOfProjectReferenceRedirect);
compilerHost.watchDirectoryOfFailedLookupLocation = (dir, cb, flags) => watchDirectory(dir, cb, flags, watchOptions, WatchType.FailedLookupLocations);
compilerHost.watchTypeRootsDirectory = (dir, cb, flags) => watchDirectory(dir, cb, flags, watchOptions, WatchType.TypeRoots);
compilerHost.getCachedDirectoryStructureHost = () => cachedDirectoryStructureHost;
compilerHost.scheduleInvalidateResolutionsOfFailedLookupLocations = scheduleInvalidateResolutionsOfFailedLookupLocations;
compilerHost.onInvalidatedResolution = scheduleProgramUpdate;
compilerHost.onChangedAutomaticTypeDirectiveNames = scheduleProgramUpdate;
compilerHost.fileIsOpen = returnFalse;
compilerHost.getCurrentProgram = getCurrentProgram;
compilerHost.writeLog = writeLog;
compilerHost.getParsedCommandLine = getParsedCommandLine;
// Cache for the module resolution
const resolutionCache = createResolutionCache(compilerHost,
configFileName ?
getDirectoryPath(getNormalizedAbsolutePath(configFileName, currentDirectory)) :
currentDirectory,
/*logChangesWhenResolvingModule*/ false
);
// Resolve module using host module resolution strategy if provided otherwise use resolution cache to resolve module names
compilerHost.resolveModuleNames = host.resolveModuleNames ?
((...args) => host.resolveModuleNames!(...args)) :
((moduleNames, containingFile, reusedNames, redirectedReference, _options, sourceFile) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, redirectedReference, sourceFile));
compilerHost.resolveTypeReferenceDirectives = host.resolveTypeReferenceDirectives ?
((...args) => host.resolveTypeReferenceDirectives!(...args)) :
((typeDirectiveNames, containingFile, redirectedReference) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile, redirectedReference));
const userProvidedResolution = !!host.resolveModuleNames || !!host.resolveTypeReferenceDirectives;
builderProgram = readBuilderProgram(compilerOptions, compilerHost) as any as T;
synchronizeProgram();
// Update the wild card directory watch
watchConfigFileWildCardDirectories();
Watch extended configs if present (#41493) * Watch extended configs if present * Address code review comments Added new `WatchType` for extended config files. Refactored watch map update to separate function, relocated call sites. Removed unnecessary test cases and relocated with new tests in programUpdates. * Unify extended config file watching between tsc/tsserver Update `updateExtendedConfigFilesWatch` to read from a `TsConfigSourceFile` to get `extendedSourceFiles`. Add watcher map to `ConfiguredProject` in the server. New test cases to verify correct events triggered and extended files are being watched properly. * Simplify watcher callback, fix tests Removes unnecessary actions in extended config watcher callback function. Updates tests to match. * Share extended config watchers across projects in server New shared watcher map in ProjectService that stores callbacks per project to be invoked when the file watcher is triggered. The FileWatcher is created with the watch options of the first Project to watch the extended config. * Refactor shared extended config map and watchers Remove all server-related utility functions/types from watchUtilities. Store config-project mapping and config file watchers inside ProjectService with new private methods to add or remove projects. * Store projects in extended config file watcher Creates SharedExtendedConfigFileWatcher in both editorServices (tsserver) and tsbuildPublic. The file watcher is responsible for triggering a full project reload for the contained projects. Upon reload, any configs that are no longer related to a project have their watchers updated to match. New test cases to confirm that the file watchers for extended configs are closed when the project is closed. * Apply suggestions from code review Co-authored-by: Sheetal Nandi <shkamat@microsoft.com> * Map extended config files by path * Move shared watcher into utilities and add more tests Co-authored-by: Sheetal Nandi <shkamat@microsoft.com>
2020-12-11 02:20:02 +01:00
// Update extended config file watch
if (configFileName) updateExtendedConfigFilesWatches(toPath(configFileName), compilerOptions, watchOptions, WatchType.ExtendedConfigFile);
Watch extended configs if present (#41493) * Watch extended configs if present * Address code review comments Added new `WatchType` for extended config files. Refactored watch map update to separate function, relocated call sites. Removed unnecessary test cases and relocated with new tests in programUpdates. * Unify extended config file watching between tsc/tsserver Update `updateExtendedConfigFilesWatch` to read from a `TsConfigSourceFile` to get `extendedSourceFiles`. Add watcher map to `ConfiguredProject` in the server. New test cases to verify correct events triggered and extended files are being watched properly. * Simplify watcher callback, fix tests Removes unnecessary actions in extended config watcher callback function. Updates tests to match. * Share extended config watchers across projects in server New shared watcher map in ProjectService that stores callbacks per project to be invoked when the file watcher is triggered. The FileWatcher is created with the watch options of the first Project to watch the extended config. * Refactor shared extended config map and watchers Remove all server-related utility functions/types from watchUtilities. Store config-project mapping and config file watchers inside ProjectService with new private methods to add or remove projects. * Store projects in extended config file watcher Creates SharedExtendedConfigFileWatcher in both editorServices (tsserver) and tsbuildPublic. The file watcher is responsible for triggering a full project reload for the contained projects. Upon reload, any configs that are no longer related to a project have their watchers updated to match. New test cases to confirm that the file watchers for extended configs are closed when the project is closed. * Apply suggestions from code review Co-authored-by: Sheetal Nandi <shkamat@microsoft.com> * Map extended config files by path * Move shared watcher into utilities and add more tests Co-authored-by: Sheetal Nandi <shkamat@microsoft.com>
2020-12-11 02:20:02 +01:00
return configFileName ?
{ getCurrentProgram: getCurrentBuilderProgram, getProgram: updateProgram, close } :
{ getCurrentProgram: getCurrentBuilderProgram, getProgram: updateProgram, updateRootFileNames, close };
function close() {
clearInvalidateResolutionsOfFailedLookupLocations();
resolutionCache.clear();
clearMap(sourceFilesCache, value => {
if (value && value.fileWatcher) {
value.fileWatcher.close();
value.fileWatcher = undefined;
}
});
if (configFileWatcher) {
configFileWatcher.close();
configFileWatcher = undefined;
}
extendedConfigCache?.clear();
extendedConfigCache = undefined;
if (sharedExtendedConfigFileWatchers) {
clearMap(sharedExtendedConfigFileWatchers, closeFileWatcherOf);
sharedExtendedConfigFileWatchers = undefined!;
Watch extended configs if present (#41493) * Watch extended configs if present * Address code review comments Added new `WatchType` for extended config files. Refactored watch map update to separate function, relocated call sites. Removed unnecessary test cases and relocated with new tests in programUpdates. * Unify extended config file watching between tsc/tsserver Update `updateExtendedConfigFilesWatch` to read from a `TsConfigSourceFile` to get `extendedSourceFiles`. Add watcher map to `ConfiguredProject` in the server. New test cases to verify correct events triggered and extended files are being watched properly. * Simplify watcher callback, fix tests Removes unnecessary actions in extended config watcher callback function. Updates tests to match. * Share extended config watchers across projects in server New shared watcher map in ProjectService that stores callbacks per project to be invoked when the file watcher is triggered. The FileWatcher is created with the watch options of the first Project to watch the extended config. * Refactor shared extended config map and watchers Remove all server-related utility functions/types from watchUtilities. Store config-project mapping and config file watchers inside ProjectService with new private methods to add or remove projects. * Store projects in extended config file watcher Creates SharedExtendedConfigFileWatcher in both editorServices (tsserver) and tsbuildPublic. The file watcher is responsible for triggering a full project reload for the contained projects. Upon reload, any configs that are no longer related to a project have their watchers updated to match. New test cases to confirm that the file watchers for extended configs are closed when the project is closed. * Apply suggestions from code review Co-authored-by: Sheetal Nandi <shkamat@microsoft.com> * Map extended config files by path * Move shared watcher into utilities and add more tests Co-authored-by: Sheetal Nandi <shkamat@microsoft.com>
2020-12-11 02:20:02 +01:00
}
if (watchedWildcardDirectories) {
clearMap(watchedWildcardDirectories, closeFileWatcherOf);
watchedWildcardDirectories = undefined!;
}
if (missingFilesMap) {
clearMap(missingFilesMap, closeFileWatcher);
missingFilesMap = undefined!;
}
if (parsedConfigs) {
clearMap(parsedConfigs, config => {
config.watcher?.close();
config.watcher = undefined;
if (config.watchedDirectories) clearMap(config.watchedDirectories, closeFileWatcherOf);
config.watchedDirectories = undefined;
});
parsedConfigs = undefined;
}
if (packageJsonMap) {
clearMap(packageJsonMap, closeFileWatcher);
packageJsonMap = undefined!;
}
}
function getCurrentBuilderProgram() {
return builderProgram;
}
function getCurrentProgram() {
return builderProgram && builderProgram.getProgramOrUndefined();
}
function synchronizeProgram() {
writeLog(`Synchronizing program`);
clearInvalidateResolutionsOfFailedLookupLocations();
const program = getCurrentBuilderProgram();
if (hasChangedCompilerOptions) {
newLine = updateNewLine();
if (program && (changesAffectResolution || changesAffectModuleResolution(program.getCompilerOptions(), compilerOptions))) {
resolutionCache.clear();
}
}
// All resolutions are invalid if user provided resolutions
const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(userProvidedResolution || changesAffectResolution);
if (isProgramUptoDate(getCurrentProgram(), rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) {
if (hasChangedConfigFileParsingErrors) {
builderProgram = createProgram(/*rootNames*/ undefined, /*options*/ undefined, compilerHost, builderProgram, configFileParsingDiagnostics, projectReferences);
hasChangedConfigFileParsingErrors = false;
}
}
else {
createNewProgram(hasInvalidatedResolution);
}
changesAffectResolution = false; // reset for next sync
if (host.afterProgramCreate && program !== builderProgram) {
host.afterProgramCreate(builderProgram);
}
return builderProgram;
}
function createNewProgram(hasInvalidatedResolution: HasInvalidatedResolution) {
// Compile the program
writeLog("CreatingProgramWith::");
writeLog(` roots: ${JSON.stringify(rootFileNames)}`);
writeLog(` options: ${JSON.stringify(compilerOptions)}`);
if (projectReferences) writeLog(` projectReferences: ${JSON.stringify(projectReferences)}`);
const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !getCurrentProgram();
hasChangedCompilerOptions = false;
hasChangedConfigFileParsingErrors = false;
resolutionCache.startCachingPerDirectoryResolution();
compilerHost.hasInvalidatedResolution = hasInvalidatedResolution;
compilerHost.hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames;
builderProgram = createProgram(rootFileNames, compilerOptions, compilerHost, builderProgram, configFileParsingDiagnostics, projectReferences);
// map package json cache entries to their realpaths so we don't try to watch across symlinks
const packageCacheEntries = map(resolutionCache.getModuleResolutionCache().getPackageJsonInfoCache().entries(), ([path, data]) => ([compilerHost.realpath ? toPath(compilerHost.realpath(path)) : path, data] as const));
resolutionCache.finishCachingPerDirectoryResolution();
// Update watches
updateMissingFilePathsWatch(builderProgram.getProgram(), missingFilesMap || (missingFilesMap = new Map()), watchMissingFilePath);
updatePackageJsonWatch(packageCacheEntries, packageJsonMap || (packageJsonMap = new Map()), watchPackageJsonLookupPath);
if (needsUpdateInTypeRootWatch) {
resolutionCache.updateTypeRootsWatch();
}
if (missingFilePathsRequestedForRelease) {
// These are the paths that program creater told us as not in use any more but were missing on the disk.
// We didnt remove the entry for them from sourceFiles cache so that we dont have to do File IO,
// if there is already watcher for it (for missing files)
// At this point our watches were updated, hence now we know that these paths are not tracked and need to be removed
// so that at later time we have correct result of their presence
for (const missingFilePath of missingFilePathsRequestedForRelease) {
if (!missingFilesMap.has(missingFilePath)) {
sourceFilesCache.delete(missingFilePath);
}
}
missingFilePathsRequestedForRelease = undefined;
}
}
function updateRootFileNames(files: string[]) {
Debug.assert(!configFileName, "Cannot update root file names with config file watch mode");
rootFileNames = files;
scheduleProgramUpdate();
}
function updateNewLine() {
return getNewLineCharacter(compilerOptions || optionsToExtendForConfigFile, () => host.getNewLine());
}
function toPath(fileName: string) {
return ts.toPath(fileName, currentDirectory, getCanonicalFileName);
}
function isFileMissingOnHost(hostSourceFile: HostFileInfo | undefined): hostSourceFile is FileMissingOnHost {
return typeof hostSourceFile === "boolean";
}
function isFilePresenceUnknownOnHost(hostSourceFile: FileMayBePresentOnHost): hostSourceFile is FilePresenceUnknownOnHost {
return typeof (hostSourceFile as FilePresenceUnknownOnHost).version === "boolean";
}
function fileExists(fileName: string) {
const path = toPath(fileName);
// If file is missing on host from cache, we can definitely say file doesnt exist
// otherwise we need to ensure from the disk
if (isFileMissingOnHost(sourceFilesCache.get(path))) {
return false;
}
return directoryStructureHost.fileExists(fileName);
}
function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined {
const hostSourceFile = sourceFilesCache.get(path);
// No source file on the host
if (isFileMissingOnHost(hostSourceFile)) {
return undefined;
}
// Create new source file if requested or the versions dont match
if (hostSourceFile === undefined || shouldCreateNewSourceFile || isFilePresenceUnknownOnHost(hostSourceFile)) {
const sourceFile = getNewSourceFile(fileName, languageVersion, onError);
if (hostSourceFile) {
if (sourceFile) {
// Set the source file and create file watcher now that file was present on the disk
(hostSourceFile as FilePresentOnHost).sourceFile = sourceFile;
hostSourceFile.version = sourceFile.version;
if (!hostSourceFile.fileWatcher) {
hostSourceFile.fileWatcher = watchFilePath(path, fileName, onSourceFileChange, PollingInterval.Low, watchOptions, WatchType.SourceFile);
}
}
else {
// There is no source file on host any more, close the watch, missing file paths will track it
if (hostSourceFile.fileWatcher) {
hostSourceFile.fileWatcher.close();
}
sourceFilesCache.set(path, false);
}
}
else {
if (sourceFile) {
const fileWatcher = watchFilePath(path, fileName, onSourceFileChange, PollingInterval.Low, watchOptions, WatchType.SourceFile);
sourceFilesCache.set(path, { sourceFile, version: sourceFile.version, fileWatcher });
}
else {
sourceFilesCache.set(path, false);
}
}
if (sourceFile) {
sourceFile.impliedNodeFormat = getImpliedNodeFormatForFile(path, resolutionCache.getModuleResolutionCache().getPackageJsonInfoCache(), compilerHost, compilerHost.getCompilationSettings());
}
return sourceFile;
}
return hostSourceFile.sourceFile;
}
function nextSourceFileVersion(path: Path) {
const hostSourceFile = sourceFilesCache.get(path);
if (hostSourceFile !== undefined) {
if (isFileMissingOnHost(hostSourceFile)) {
// The next version, lets set it as presence unknown file
sourceFilesCache.set(path, { version: false });
}
else {
(hostSourceFile as FilePresenceUnknownOnHost).version = false;
}
}
}
function getSourceVersion(path: Path): string | undefined {
const hostSourceFile = sourceFilesCache.get(path);
return !hostSourceFile || !hostSourceFile.version ? undefined : hostSourceFile.version;
}
function onReleaseOldSourceFile(oldSourceFile: SourceFile, _oldOptions: CompilerOptions, hasSourceFileByPath: boolean) {
const hostSourceFileInfo = sourceFilesCache.get(oldSourceFile.resolvedPath);
// If this is the source file thats in the cache and new program doesnt need it,
// remove the cached entry.
// Note we arent deleting entry if file became missing in new program or
// there was version update and new source file was created.
if (hostSourceFileInfo !== undefined) {
// record the missing file paths so they can be removed later if watchers arent tracking them
if (isFileMissingOnHost(hostSourceFileInfo)) {
(missingFilePathsRequestedForRelease || (missingFilePathsRequestedForRelease = [])).push(oldSourceFile.path);
}
else if ((hostSourceFileInfo as FilePresentOnHost).sourceFile === oldSourceFile) {
if (hostSourceFileInfo.fileWatcher) {
hostSourceFileInfo.fileWatcher.close();
}
sourceFilesCache.delete(oldSourceFile.resolvedPath);
if (!hasSourceFileByPath) {
resolutionCache.removeResolutionsOfFile(oldSourceFile.path);
}
}
}
}
function reportWatchDiagnostic(message: DiagnosticMessage) {
if (host.onWatchStatusChange) {
host.onWatchStatusChange(createCompilerDiagnostic(message), newLine, compilerOptions || optionsToExtendForConfigFile);
}
}
function hasChangedAutomaticTypeDirectiveNames() {
return resolutionCache.hasChangedAutomaticTypeDirectiveNames();
}
function clearInvalidateResolutionsOfFailedLookupLocations() {
if (!timerToInvalidateFailedLookupResolutions) return false;
host.clearTimeout!(timerToInvalidateFailedLookupResolutions);
timerToInvalidateFailedLookupResolutions = undefined;
return true;
}
function scheduleInvalidateResolutionsOfFailedLookupLocations() {
if (!host.setTimeout || !host.clearTimeout) {
return resolutionCache.invalidateResolutionsOfFailedLookupLocations();
}
const pending = clearInvalidateResolutionsOfFailedLookupLocations();
writeLog(`Scheduling invalidateFailedLookup${pending ? ", Cancelled earlier one" : ""}`);
timerToInvalidateFailedLookupResolutions = host.setTimeout(invalidateResolutionsOfFailedLookup, 250);
}
function invalidateResolutionsOfFailedLookup() {
timerToInvalidateFailedLookupResolutions = undefined;
if (resolutionCache.invalidateResolutionsOfFailedLookupLocations()) {
scheduleProgramUpdate();
}
}
// Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch
// operations (such as saving all modified files in an editor) a chance to complete before we kick
// off a new compilation.
function scheduleProgramUpdate() {
if (!host.setTimeout || !host.clearTimeout) {
return;
}
if (timerToUpdateProgram) {
host.clearTimeout(timerToUpdateProgram);
}
writeLog("Scheduling update");
timerToUpdateProgram = host.setTimeout(updateProgramWithWatchStatus, 250);
}
function scheduleProgramReload() {
Debug.assert(!!configFileName);
reloadLevel = ConfigFileProgramReloadLevel.Full;
scheduleProgramUpdate();
}
function updateProgramWithWatchStatus() {
timerToUpdateProgram = undefined;
reportWatchDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation);
updateProgram();
}
function updateProgram() {
switch (reloadLevel) {
case ConfigFileProgramReloadLevel.Partial:
perfLogger.logStartUpdateProgram("PartialConfigReload");
reloadFileNamesFromConfigFile();
break;
case ConfigFileProgramReloadLevel.Full:
perfLogger.logStartUpdateProgram("FullConfigReload");
reloadConfigFile();
break;
default:
perfLogger.logStartUpdateProgram("SynchronizeProgram");
synchronizeProgram();
break;
}
perfLogger.logStopUpdateProgram("Done");
return getCurrentBuilderProgram();
}
function reloadFileNamesFromConfigFile() {
writeLog("Reloading new file names and options");
rootFileNames = getFileNamesFromConfigSpecs(compilerOptions.configFile!.configFileSpecs!, getNormalizedAbsolutePath(getDirectoryPath(configFileName), currentDirectory), compilerOptions, parseConfigFileHost, extraFileExtensions);
if (updateErrorForNoInputFiles(rootFileNames, getNormalizedAbsolutePath(configFileName, currentDirectory), compilerOptions.configFile!.configFileSpecs!, configFileParsingDiagnostics!, canConfigFileJsonReportNoInputFiles)) {
hasChangedConfigFileParsingErrors = true;
}
// Update the program
synchronizeProgram();
}
function reloadConfigFile() {
writeLog(`Reloading config file: ${configFileName}`);
reloadLevel = ConfigFileProgramReloadLevel.None;
if (cachedDirectoryStructureHost) {
cachedDirectoryStructureHost.clearCache();
}
parseConfigFile();
hasChangedCompilerOptions = true;
synchronizeProgram();
// Update the wild card directory watch
watchConfigFileWildCardDirectories();
Watch extended configs if present (#41493) * Watch extended configs if present * Address code review comments Added new `WatchType` for extended config files. Refactored watch map update to separate function, relocated call sites. Removed unnecessary test cases and relocated with new tests in programUpdates. * Unify extended config file watching between tsc/tsserver Update `updateExtendedConfigFilesWatch` to read from a `TsConfigSourceFile` to get `extendedSourceFiles`. Add watcher map to `ConfiguredProject` in the server. New test cases to verify correct events triggered and extended files are being watched properly. * Simplify watcher callback, fix tests Removes unnecessary actions in extended config watcher callback function. Updates tests to match. * Share extended config watchers across projects in server New shared watcher map in ProjectService that stores callbacks per project to be invoked when the file watcher is triggered. The FileWatcher is created with the watch options of the first Project to watch the extended config. * Refactor shared extended config map and watchers Remove all server-related utility functions/types from watchUtilities. Store config-project mapping and config file watchers inside ProjectService with new private methods to add or remove projects. * Store projects in extended config file watcher Creates SharedExtendedConfigFileWatcher in both editorServices (tsserver) and tsbuildPublic. The file watcher is responsible for triggering a full project reload for the contained projects. Upon reload, any configs that are no longer related to a project have their watchers updated to match. New test cases to confirm that the file watchers for extended configs are closed when the project is closed. * Apply suggestions from code review Co-authored-by: Sheetal Nandi <shkamat@microsoft.com> * Map extended config files by path * Move shared watcher into utilities and add more tests Co-authored-by: Sheetal Nandi <shkamat@microsoft.com>
2020-12-11 02:20:02 +01:00
// Update extended config file watch
updateExtendedConfigFilesWatches(toPath(configFileName), compilerOptions, watchOptions, WatchType.ExtendedConfigFile);
}
function parseConfigFile() {
setConfigFileParsingResult(getParsedCommandLineOfConfigFile(
configFileName,
optionsToExtendForConfigFile,
parseConfigFileHost,
extendedConfigCache ||= new Map(),
watchOptionsToExtend,
extraFileExtensions
)!); // TODO: GH#18217
}
function setConfigFileParsingResult(configFileParseResult: ParsedCommandLine) {
rootFileNames = configFileParseResult.fileNames;
compilerOptions = configFileParseResult.options;
watchOptions = configFileParseResult.watchOptions;
projectReferences = configFileParseResult.projectReferences;
wildcardDirectories = configFileParseResult.wildcardDirectories;
configFileParsingDiagnostics = getConfigFileParsingDiagnostics(configFileParseResult).slice();
Expand auto-import to all package.json dependencies (#38923) * Start experiment * Add logging * Go back to a single program * Fix forEachExternalModuleToImportFrom * Move auxiliary program to language service * Add logging * Don’t use resolution cache * Fix(?) containingProjects for ScriptInfo in auxiliary program * Fix ScriptInfo project inclusion * Add test for default project of auto-importable ScriptInfo * Add fourslash server test * Don’t create auto import provider inside node_modules * Add monorepo-like test * WIP * Naively ensure autoImportProvider is up to date after package.json change * Start limiting when auto update provider gets updated * Respond to changes in node_modules * Don’t create auto-import provider until a file is open that would use it e.g., don’t create them during cross-project find-all-refs * Clean up naming, @internal marking, and fix empty project creation bug * Drop devDependencies, include peerDependencies * Add additional compiler options * Fix interaction with importSuggestionsCache * Move option to UserPreferences, allow inclusion of devDependencies * Don’t filter out peerDependencies * Watch unparseable package.jsons * But don’t filter packages out due to an invalid package.json * Update test * Don’t use autoImportProvider in codefixes where it can never be used (or any refactors) * Add CompletionEntry property for telemetry * Add assertion for isPackageJsonImport to fourslash * Fix missing pushSymbol argument * Add isPackageJsonImport to tests and API baselines * Fix unit test * Host auto import provider in new Project kind * Fix InferredProject attaching on AutoImportProvider-included files, load eagerly * Update Public APIs * Simplify PackageJsonCache host * Remove unneeded markAsDirty * Defer project finished event until after AutoImportProvider is created * Make AutoImportProviderProject always report isOrphan = true * Close and remove AutoImportProviderProject when host project closes * Don’t set pendingEnsureProjectForOpenFiles * Use hasAddedOrRemovedFiles instead of hasNewProgram * Use host-wide watchOptions for package.json watching * Add to `printProjects` * Clean up * Get autoImportProvider directly from LanguageServiceHost * Clean up * Clean up * Close auto import provider on disableLanguageService * Move AutoImportProvider preload to project updateGraph * Clear auto import suggestion cache when provider program changes * Fix tests * Revert yet-unneeded change * Use projectService host for module resolution host * Don’t re-resolve type directives if nothing has changed * Update src/server/project.ts Co-authored-by: Sheetal Nandi <shkamat@microsoft.com> * Use ts.emptyArray Co-authored-by: Sheetal Nandi <shkamat@microsoft.com>
2020-06-23 01:34:27 +02:00
canConfigFileJsonReportNoInputFiles = canJsonReportNoInputFiles(configFileParseResult.raw);
hasChangedConfigFileParsingErrors = true;
}
function getParsedCommandLine(configFileName: string): ParsedCommandLine | undefined {
const configPath = toPath(configFileName);
let config = parsedConfigs?.get(configPath);
if (config) {
if (!config.reloadLevel) return config.parsedCommandLine;
// With host implementing getParsedCommandLine we cant just update file names
if (config.parsedCommandLine && config.reloadLevel === ConfigFileProgramReloadLevel.Partial && !host.getParsedCommandLine) {
writeLog("Reloading new file names and options");
const fileNames = getFileNamesFromConfigSpecs(
config.parsedCommandLine.options.configFile!.configFileSpecs!,
getNormalizedAbsolutePath(getDirectoryPath(configFileName), currentDirectory),
compilerOptions,
parseConfigFileHost,
);
config.parsedCommandLine = { ...config.parsedCommandLine, fileNames };
config.reloadLevel = undefined;
return config.parsedCommandLine;
}
}
writeLog(`Loading config file: ${configFileName}`);
const parsedCommandLine = host.getParsedCommandLine ?
host.getParsedCommandLine(configFileName) :
getParsedCommandLineFromConfigFileHost(configFileName);
if (config) {
config.parsedCommandLine = parsedCommandLine;
config.reloadLevel = undefined;
}
else {
(parsedConfigs ||= new Map()).set(configPath, config = { parsedCommandLine });
}
watchReferencedProject(configFileName, configPath, config);
return parsedCommandLine;
}
function getParsedCommandLineFromConfigFileHost(configFileName: string) {
// Ignore the file absent errors
const onUnRecoverableConfigFileDiagnostic = parseConfigFileHost.onUnRecoverableConfigFileDiagnostic;
parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = noop;
const parsedCommandLine = getParsedCommandLineOfConfigFile(
configFileName,
/*optionsToExtend*/ undefined,
parseConfigFileHost,
extendedConfigCache ||= new Map(),
watchOptionsToExtend
);
parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = onUnRecoverableConfigFileDiagnostic;
return parsedCommandLine;
}
function onReleaseParsedCommandLine(fileName: string) {
const path = toPath(fileName);
const config = parsedConfigs?.get(path);
if (!config) return;
parsedConfigs!.delete(path);
if (config.watchedDirectories) clearMap(config.watchedDirectories, closeFileWatcherOf);
config.watcher?.close();
clearSharedExtendedConfigFileWatcher(path, sharedExtendedConfigFileWatchers);
}
function watchFilePath(
path: Path,
file: string,
callback: (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void,
pollingInterval: PollingInterval,
options: WatchOptions | undefined,
watchType: WatchType
): FileWatcher {
return watchFile(file, (fileName, eventKind) => callback(fileName, eventKind, path), pollingInterval, options, watchType);
}
function onSourceFileChange(fileName: string, eventKind: FileWatcherEventKind, path: Path) {
updateCachedSystemWithFile(fileName, path, eventKind);
// Update the source file cache
if (eventKind === FileWatcherEventKind.Deleted && sourceFilesCache.has(path)) {
resolutionCache.invalidateResolutionOfFile(path);
}
nextSourceFileVersion(path);
// Update the program
scheduleProgramUpdate();
}
function updateCachedSystemWithFile(fileName: string, path: Path, eventKind: FileWatcherEventKind) {
if (cachedDirectoryStructureHost) {
cachedDirectoryStructureHost.addOrDeleteFile(fileName, path, eventKind);
}
}
function watchMissingFilePath(missingFilePath: Path) {
// If watching missing referenced config file, we are already watching it so no need for separate watcher
return parsedConfigs?.has(missingFilePath) ?
noopFileWatcher :
watchFilePath(missingFilePath, missingFilePath, onMissingFileChange, PollingInterval.Medium, watchOptions, WatchType.MissingFile);
}
function watchPackageJsonLookupPath(packageJsonPath: Path) {
// If the package.json is pulled into the compilation itself (eg, via json imports), don't add a second watcher here
return sourceFilesCache.has(packageJsonPath) ?
noopFileWatcher :
watchFilePath(packageJsonPath, packageJsonPath, onPackageJsonChange, PollingInterval.High, watchOptions, WatchType.PackageJson);
}
function onPackageJsonChange(fileName: string, eventKind: FileWatcherEventKind, path: Path) {
updateCachedSystemWithFile(fileName, path, eventKind);
// package.json changes invalidate module resolution and can change the set of loaded files
// so if we witness a change to one, we have to do a full reload
reloadLevel = ConfigFileProgramReloadLevel.Full;
changesAffectResolution = true;
// Update the program
scheduleProgramUpdate();
}
function onMissingFileChange(fileName: string, eventKind: FileWatcherEventKind, missingFilePath: Path) {
updateCachedSystemWithFile(fileName, missingFilePath, eventKind);
if (eventKind === FileWatcherEventKind.Created && missingFilesMap.has(missingFilePath)) {
missingFilesMap.get(missingFilePath)!.close();
missingFilesMap.delete(missingFilePath);
// Delete the entry in the source files cache so that new source file is created
nextSourceFileVersion(missingFilePath);
// When a missing file is created, we should update the graph.
scheduleProgramUpdate();
}
}
function watchConfigFileWildCardDirectories() {
if (wildcardDirectories) {
updateWatchingWildcardDirectories(
2020-06-26 01:03:25 +02:00
watchedWildcardDirectories || (watchedWildcardDirectories = new Map()),
new Map(getEntries(wildcardDirectories)),
watchWildcardDirectory
);
}
else if (watchedWildcardDirectories) {
clearMap(watchedWildcardDirectories, closeFileWatcherOf);
}
}
function watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags) {
return watchDirectory(
directory,
fileOrDirectory => {
Debug.assert(!!configFileName);
const fileOrDirectoryPath = toPath(fileOrDirectory);
// Since the file existence changed, update the sourceFiles cache
if (cachedDirectoryStructureHost) {
cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
}
nextSourceFileVersion(fileOrDirectoryPath);
if (isIgnoredFileFromWildCardWatching({
watchedDirPath: toPath(directory),
fileOrDirectory,
fileOrDirectoryPath,
configFileName,
extraFileExtensions,
options: compilerOptions,
program: getCurrentBuilderProgram() || rootFileNames,
currentDirectory,
useCaseSensitiveFileNames,
writeLog,
toPath,
})) return;
// Reload is pending, do the reload
if (reloadLevel !== ConfigFileProgramReloadLevel.Full) {
reloadLevel = ConfigFileProgramReloadLevel.Partial;
// Schedule Update the program
scheduleProgramUpdate();
}
},
flags,
watchOptions,
WatchType.WildcardDirectory
);
}
Watch extended configs if present (#41493) * Watch extended configs if present * Address code review comments Added new `WatchType` for extended config files. Refactored watch map update to separate function, relocated call sites. Removed unnecessary test cases and relocated with new tests in programUpdates. * Unify extended config file watching between tsc/tsserver Update `updateExtendedConfigFilesWatch` to read from a `TsConfigSourceFile` to get `extendedSourceFiles`. Add watcher map to `ConfiguredProject` in the server. New test cases to verify correct events triggered and extended files are being watched properly. * Simplify watcher callback, fix tests Removes unnecessary actions in extended config watcher callback function. Updates tests to match. * Share extended config watchers across projects in server New shared watcher map in ProjectService that stores callbacks per project to be invoked when the file watcher is triggered. The FileWatcher is created with the watch options of the first Project to watch the extended config. * Refactor shared extended config map and watchers Remove all server-related utility functions/types from watchUtilities. Store config-project mapping and config file watchers inside ProjectService with new private methods to add or remove projects. * Store projects in extended config file watcher Creates SharedExtendedConfigFileWatcher in both editorServices (tsserver) and tsbuildPublic. The file watcher is responsible for triggering a full project reload for the contained projects. Upon reload, any configs that are no longer related to a project have their watchers updated to match. New test cases to confirm that the file watchers for extended configs are closed when the project is closed. * Apply suggestions from code review Co-authored-by: Sheetal Nandi <shkamat@microsoft.com> * Map extended config files by path * Move shared watcher into utilities and add more tests Co-authored-by: Sheetal Nandi <shkamat@microsoft.com>
2020-12-11 02:20:02 +01:00
function updateExtendedConfigFilesWatches(forProjectPath: Path, options: CompilerOptions | undefined, watchOptions: WatchOptions | undefined, watchType: WatchTypeRegistry["ExtendedConfigFile"] | WatchTypeRegistry["ExtendedConfigOfReferencedProject"]) {
updateSharedExtendedConfigFileWatcher(
forProjectPath,
options,
sharedExtendedConfigFileWatchers ||= new Map(),
(extendedConfigFileName, extendedConfigFilePath) => watchFile(
extendedConfigFileName,
(_fileName, eventKind) => {
updateCachedSystemWithFile(extendedConfigFileName, extendedConfigFilePath, eventKind);
// Update extended config cache
if (extendedConfigCache) cleanExtendedConfigCache(extendedConfigCache, extendedConfigFilePath, toPath);
// Update projects
const projects = sharedExtendedConfigFileWatchers.get(extendedConfigFilePath)?.projects;
// If there are no referenced projects this extended config file watcher depend on ignore
if (!projects?.size) return;
projects.forEach(projectPath => {
if (toPath(configFileName) === projectPath) {
// If this is the config file of the project, reload completely
reloadLevel = ConfigFileProgramReloadLevel.Full;
}
else {
// Reload config for the referenced projects and remove the resolutions from referenced projects since the config file changed
const config = parsedConfigs?.get(projectPath);
if (config) config.reloadLevel = ConfigFileProgramReloadLevel.Full;
resolutionCache.removeResolutionsFromProjectReferenceRedirects(projectPath);
}
scheduleProgramUpdate();
});
},
PollingInterval.High,
watchOptions,
watchType
),
toPath,
Watch extended configs if present (#41493) * Watch extended configs if present * Address code review comments Added new `WatchType` for extended config files. Refactored watch map update to separate function, relocated call sites. Removed unnecessary test cases and relocated with new tests in programUpdates. * Unify extended config file watching between tsc/tsserver Update `updateExtendedConfigFilesWatch` to read from a `TsConfigSourceFile` to get `extendedSourceFiles`. Add watcher map to `ConfiguredProject` in the server. New test cases to verify correct events triggered and extended files are being watched properly. * Simplify watcher callback, fix tests Removes unnecessary actions in extended config watcher callback function. Updates tests to match. * Share extended config watchers across projects in server New shared watcher map in ProjectService that stores callbacks per project to be invoked when the file watcher is triggered. The FileWatcher is created with the watch options of the first Project to watch the extended config. * Refactor shared extended config map and watchers Remove all server-related utility functions/types from watchUtilities. Store config-project mapping and config file watchers inside ProjectService with new private methods to add or remove projects. * Store projects in extended config file watcher Creates SharedExtendedConfigFileWatcher in both editorServices (tsserver) and tsbuildPublic. The file watcher is responsible for triggering a full project reload for the contained projects. Upon reload, any configs that are no longer related to a project have their watchers updated to match. New test cases to confirm that the file watchers for extended configs are closed when the project is closed. * Apply suggestions from code review Co-authored-by: Sheetal Nandi <shkamat@microsoft.com> * Map extended config files by path * Move shared watcher into utilities and add more tests Co-authored-by: Sheetal Nandi <shkamat@microsoft.com>
2020-12-11 02:20:02 +01:00
);
}
function watchReferencedProject(configFileName: string, configPath: Path, commandLine: ParsedConfig) {
// Watch file
commandLine.watcher ||= watchFile(
configFileName,
(_fileName, eventKind) => {
updateCachedSystemWithFile(configFileName, configPath, eventKind);
const config = parsedConfigs?.get(configPath);
if (config) config.reloadLevel = ConfigFileProgramReloadLevel.Full;
resolutionCache.removeResolutionsFromProjectReferenceRedirects(configPath);
scheduleProgramUpdate();
},
PollingInterval.High,
commandLine.parsedCommandLine?.watchOptions || watchOptions,
WatchType.ConfigFileOfReferencedProject
);
// Watch Wild card
if (commandLine.parsedCommandLine?.wildcardDirectories) {
updateWatchingWildcardDirectories(
commandLine.watchedDirectories ||= new Map(),
new Map(getEntries(commandLine.parsedCommandLine?.wildcardDirectories)),
(directory, flags) => watchDirectory(
directory,
fileOrDirectory => {
const fileOrDirectoryPath = toPath(fileOrDirectory);
// Since the file existence changed, update the sourceFiles cache
if (cachedDirectoryStructureHost) {
cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
}
nextSourceFileVersion(fileOrDirectoryPath);
const config = parsedConfigs?.get(configPath);
if (!config?.parsedCommandLine) return;
if (isIgnoredFileFromWildCardWatching({
watchedDirPath: toPath(directory),
fileOrDirectory,
fileOrDirectoryPath,
configFileName,
options: config.parsedCommandLine.options,
program: config.parsedCommandLine.fileNames,
currentDirectory,
useCaseSensitiveFileNames,
writeLog,
toPath,
})) return;
// Reload is pending, do the reload
if (config.reloadLevel !== ConfigFileProgramReloadLevel.Full) {
config.reloadLevel = ConfigFileProgramReloadLevel.Partial;
// Schedule Update the program
scheduleProgramUpdate();
}
},
flags,
commandLine.parsedCommandLine?.watchOptions || watchOptions,
WatchType.WildcardDirectoryOfReferencedProject
)
);
}
else if (commandLine.watchedDirectories) {
clearMap(commandLine.watchedDirectories, closeFileWatcherOf);
commandLine.watchedDirectories = undefined;
}
// Watch extended config files
updateExtendedConfigFilesWatches(
configPath,
commandLine.parsedCommandLine?.options,
commandLine.parsedCommandLine?.watchOptions || watchOptions,
WatchType.ExtendedConfigOfReferencedProject
);
Watch extended configs if present (#41493) * Watch extended configs if present * Address code review comments Added new `WatchType` for extended config files. Refactored watch map update to separate function, relocated call sites. Removed unnecessary test cases and relocated with new tests in programUpdates. * Unify extended config file watching between tsc/tsserver Update `updateExtendedConfigFilesWatch` to read from a `TsConfigSourceFile` to get `extendedSourceFiles`. Add watcher map to `ConfiguredProject` in the server. New test cases to verify correct events triggered and extended files are being watched properly. * Simplify watcher callback, fix tests Removes unnecessary actions in extended config watcher callback function. Updates tests to match. * Share extended config watchers across projects in server New shared watcher map in ProjectService that stores callbacks per project to be invoked when the file watcher is triggered. The FileWatcher is created with the watch options of the first Project to watch the extended config. * Refactor shared extended config map and watchers Remove all server-related utility functions/types from watchUtilities. Store config-project mapping and config file watchers inside ProjectService with new private methods to add or remove projects. * Store projects in extended config file watcher Creates SharedExtendedConfigFileWatcher in both editorServices (tsserver) and tsbuildPublic. The file watcher is responsible for triggering a full project reload for the contained projects. Upon reload, any configs that are no longer related to a project have their watchers updated to match. New test cases to confirm that the file watchers for extended configs are closed when the project is closed. * Apply suggestions from code review Co-authored-by: Sheetal Nandi <shkamat@microsoft.com> * Map extended config files by path * Move shared watcher into utilities and add more tests Co-authored-by: Sheetal Nandi <shkamat@microsoft.com>
2020-12-11 02:20:02 +01:00
}
}
}