mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-30 01:04:49 -05:00
Handle watches of missing directories and make project the module resolution host
This commit is contained in:
@@ -5,8 +5,6 @@
|
||||
namespace ts {
|
||||
/** This is the cache of module/typedirectives resolution that can be retained across program */
|
||||
export interface ResolutionCache {
|
||||
setModuleResolutionHost(host: ModuleResolutionHost): void;
|
||||
|
||||
startRecordingFilesWithChangedResolutions(): void;
|
||||
finishRecordingFilesWithChangedResolutions(): Path[];
|
||||
|
||||
@@ -14,8 +12,6 @@ namespace ts {
|
||||
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
|
||||
|
||||
invalidateResolutionOfFile(filePath: Path): void;
|
||||
onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: Path): boolean;
|
||||
|
||||
createHasInvalidatedResolution(): HasInvalidatedResolution;
|
||||
|
||||
clear(): void;
|
||||
@@ -33,15 +29,17 @@ namespace ts {
|
||||
mapLocations: MultiMap<string>;
|
||||
}
|
||||
|
||||
export function createResolutionCache(
|
||||
toPath: (fileName: string) => Path,
|
||||
getCompilerOptions: () => CompilerOptions,
|
||||
watchDirectoryOfFailedLookupLocation: (directory: string) => FileWatcher,
|
||||
log: (s: string) => void,
|
||||
projectName?: string,
|
||||
getGlobalCache?: () => string | undefined): ResolutionCache {
|
||||
export interface ResolutionCacheHost extends ModuleResolutionHost {
|
||||
toPath(fileName: string): Path;
|
||||
getCompilationSettings(): CompilerOptions;
|
||||
watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback): FileWatcher;
|
||||
onInvalidatedResolution(): void;
|
||||
getCachedPartialSystem?(): CachedPartialSystem;
|
||||
projectName?: string;
|
||||
getGlobalCache?(): string | undefined;
|
||||
}
|
||||
|
||||
let host: ModuleResolutionHost;
|
||||
export function createResolutionCache(resolutionHost: ResolutionCacheHost): ResolutionCache {
|
||||
let filesWithChangedSetOfUnresolvedImports: Path[] | undefined;
|
||||
let filesWithInvalidatedResolutions: Map<true> | undefined;
|
||||
|
||||
@@ -52,23 +50,16 @@ namespace ts {
|
||||
const resolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
|
||||
|
||||
const directoryWatchesOfFailedLookups = createMap<DirectoryWatchesOfFailedLookup>();
|
||||
|
||||
return {
|
||||
setModuleResolutionHost,
|
||||
startRecordingFilesWithChangedResolutions,
|
||||
finishRecordingFilesWithChangedResolutions,
|
||||
resolveModuleNames,
|
||||
resolveTypeReferenceDirectives,
|
||||
invalidateResolutionOfFile,
|
||||
onFileAddOrRemoveInDirectoryOfFailedLookup,
|
||||
createHasInvalidatedResolution,
|
||||
clear
|
||||
};
|
||||
|
||||
function setModuleResolutionHost(updatedHost: ModuleResolutionHost) {
|
||||
host = updatedHost;
|
||||
}
|
||||
|
||||
function clear() {
|
||||
// Close all the watches for failed lookup locations, irrespective of refcounts for them since this is to clear the cache
|
||||
clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf);
|
||||
@@ -95,16 +86,16 @@ namespace ts {
|
||||
function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
|
||||
const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host);
|
||||
// return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts
|
||||
if (!getGlobalCache) {
|
||||
if (!resolutionHost.getGlobalCache) {
|
||||
return primaryResult;
|
||||
}
|
||||
|
||||
// otherwise try to load typings from @types
|
||||
const globalCache = getGlobalCache();
|
||||
const globalCache = resolutionHost.getGlobalCache();
|
||||
if (globalCache !== undefined && !isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTypeScript(primaryResult.resolvedModule.extension))) {
|
||||
// create different collection of failed lookup locations for second pass
|
||||
// if it will fail and we've already found something during the first pass - we don't want to pollute its results
|
||||
const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(moduleName, projectName, compilerOptions, host, globalCache);
|
||||
const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(moduleName, resolutionHost.projectName, compilerOptions, host, globalCache);
|
||||
if (resolvedModule) {
|
||||
return { resolvedModule, failedLookupLocations: addRange(primaryResult.failedLookupLocations as Array<string>, failedLookupLocations) };
|
||||
}
|
||||
@@ -123,12 +114,12 @@ namespace ts {
|
||||
getResultFileName: (result: R) => string | undefined,
|
||||
logChanges: boolean): R[] {
|
||||
|
||||
const path = toPath(containingFile);
|
||||
const path = resolutionHost.toPath(containingFile);
|
||||
const currentResolutionsInFile = cache.get(path);
|
||||
|
||||
const newResolutions: Map<T> = createMap<T>();
|
||||
const resolvedModules: R[] = [];
|
||||
const compilerOptions = getCompilerOptions();
|
||||
const compilerOptions = resolutionHost.getCompilationSettings();
|
||||
|
||||
for (const name of names) {
|
||||
// check if this is a duplicate entry in the list
|
||||
@@ -140,7 +131,7 @@ namespace ts {
|
||||
resolution = existingResolution;
|
||||
}
|
||||
else {
|
||||
resolution = loader(name, containingFile, compilerOptions, host);
|
||||
resolution = loader(name, containingFile, compilerOptions, resolutionHost);
|
||||
updateFailedLookupLocationWatches(resolution.failedLookupLocations, existingResolution && existingResolution.failedLookupLocations);
|
||||
}
|
||||
newResolutions.set(name, resolution);
|
||||
@@ -214,12 +205,31 @@ namespace ts {
|
||||
const mapLocations = createMultiMap<string>();
|
||||
mapLocations.add(failedLookupLocationPath, failedLookupLocation);
|
||||
directoryWatchesOfFailedLookups.set(dirPath, {
|
||||
watcher: watchDirectoryOfFailedLookupLocation(getDirectoryPath(failedLookupLocation)),
|
||||
watcher: createDirectoryWatcher(getDirectoryPath(failedLookupLocation), dirPath),
|
||||
mapLocations
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function createDirectoryWatcher(directory: string, dirPath: Path) {
|
||||
return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrFolder => {
|
||||
const fileOrFolderPath = resolutionHost.toPath(fileOrFolder);
|
||||
if (resolutionHost.getCachedPartialSystem) {
|
||||
// Since the file existance changed, update the sourceFiles cache
|
||||
resolutionHost.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath);
|
||||
}
|
||||
|
||||
// If the location results in update to failed lookup, schedule program update
|
||||
if (dirPath === fileOrFolderPath) {
|
||||
onAddOrRemoveDirectoryOfFailedLookup(dirPath);
|
||||
resolutionHost.onInvalidatedResolution();
|
||||
}
|
||||
else if (onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolderPath)) {
|
||||
resolutionHost.onInvalidatedResolution();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeFailedLookupLocationWatcher(failedLookupLocation: string, failedLookupLocationPath: Path) {
|
||||
const dirPath = getDirectoryPath(failedLookupLocationPath);
|
||||
const watches = directoryWatchesOfFailedLookups.get(dirPath);
|
||||
@@ -234,13 +244,12 @@ namespace ts {
|
||||
function withFailedLookupLocations(failedLookupLocations: ReadonlyArray<string> | undefined, fn: FailedLookupLocationAction, startIndex?: number) {
|
||||
if (failedLookupLocations) {
|
||||
for (let i = startIndex || 0; i < failedLookupLocations.length; i++) {
|
||||
fn(failedLookupLocations[i], toPath(failedLookupLocations[i]));
|
||||
fn(failedLookupLocations[i], resolutionHost.toPath(failedLookupLocations[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateFailedLookupLocationWatches(failedLookupLocations: ReadonlyArray<string> | undefined, existingFailedLookupLocations: ReadonlyArray<string> | undefined) {
|
||||
log(`Resolution cache: Updating...`);
|
||||
const index = existingFailedLookupLocations && failedLookupLocations ?
|
||||
findDiffIndex(failedLookupLocations, existingFailedLookupLocations) :
|
||||
0;
|
||||
@@ -269,7 +278,7 @@ namespace ts {
|
||||
if (resolution && !resolution.isInvalidated) {
|
||||
const result = getResult(resolution);
|
||||
if (result) {
|
||||
if (toPath(getResultFileName(result)) === deletedFilePath) {
|
||||
if (resolutionHost.toPath(getResultFileName(result)) === deletedFilePath) {
|
||||
resolution.isInvalidated = true;
|
||||
(filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap<true>())).set(path, true);
|
||||
}
|
||||
@@ -281,12 +290,13 @@ namespace ts {
|
||||
}
|
||||
|
||||
function invalidateResolutionCacheOfChangedFailedLookupLocation<T extends NameResolutionWithFailedLookupLocations>(
|
||||
failedLookupLocationPath: Path,
|
||||
cache: Map<Map<T>>) {
|
||||
cache: Map<Map<T>>,
|
||||
isChangedFailedLookupLocation: (location: string) => boolean
|
||||
) {
|
||||
cache.forEach((value, containingFile) => {
|
||||
if (value) {
|
||||
value.forEach(resolution => {
|
||||
if (resolution && !resolution.isInvalidated && some(resolution.failedLookupLocations, location => toPath(location) === failedLookupLocationPath)) {
|
||||
if (resolution && !resolution.isInvalidated && some(resolution.failedLookupLocations, isChangedFailedLookupLocation)) {
|
||||
// Mark the file as needing re-evaluation of module resolution instead of using it blindly.
|
||||
resolution.isInvalidated = true;
|
||||
(filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = createMap<true>())).set(containingFile, true);
|
||||
@@ -306,10 +316,17 @@ namespace ts {
|
||||
const watches = directoryWatchesOfFailedLookups.get(dirPath);
|
||||
const isFailedLookupFile = watches.mapLocations.has(fileOrFolder);
|
||||
if (isFailedLookupFile) {
|
||||
invalidateResolutionCacheOfChangedFailedLookupLocation(fileOrFolder, resolvedModuleNames);
|
||||
invalidateResolutionCacheOfChangedFailedLookupLocation(fileOrFolder, resolvedTypeReferenceDirectives);
|
||||
const isFileOrFolder: (location: string) => boolean = location => resolutionHost.toPath(location) === fileOrFolder;
|
||||
invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedModuleNames, isFileOrFolder);
|
||||
invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedTypeReferenceDirectives, isFileOrFolder);
|
||||
}
|
||||
return isFailedLookupFile;
|
||||
}
|
||||
|
||||
function onAddOrRemoveDirectoryOfFailedLookup(dirPath: Path) {
|
||||
const isInDirPath: (location: string) => boolean = location => getDirectoryPath(resolutionHost.toPath(location)) === dirPath;
|
||||
invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedModuleNames, isInDirPath);
|
||||
invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedTypeReferenceDirectives, isInDirPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,11 +161,10 @@ namespace ts {
|
||||
watcher.referenceCount += 1;
|
||||
return;
|
||||
}
|
||||
watcher = _fs.watch(
|
||||
watcher = fsWatchDirectory(
|
||||
dirPath || ".",
|
||||
{ persistent: true },
|
||||
(eventName: string, relativeFileName: string) => fileEventHandler(eventName, relativeFileName, dirPath)
|
||||
);
|
||||
) as DirectoryWatcher;
|
||||
watcher.referenceCount = 1;
|
||||
dirWatchers.set(dirPath, watcher);
|
||||
return;
|
||||
@@ -232,6 +231,83 @@ namespace ts {
|
||||
const platform: string = _os.platform();
|
||||
const useCaseSensitiveFileNames = isFileSystemCaseSensitive();
|
||||
|
||||
function fsWatchFile(fileName: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher {
|
||||
_fs.watchFile(fileName, { persistent: true, interval: pollingInterval || 250 }, fileChanged);
|
||||
return {
|
||||
close: () => _fs.unwatchFile(fileName, fileChanged)
|
||||
};
|
||||
|
||||
function fileChanged(curr: any, prev: any) {
|
||||
const isCurrZero = +curr.mtime === 0;
|
||||
const isPrevZero = +prev.mtime === 0;
|
||||
const created = !isCurrZero && isPrevZero;
|
||||
const deleted = isCurrZero && !isPrevZero;
|
||||
|
||||
const eventKind = created
|
||||
? FileWatcherEventKind.Created
|
||||
: deleted
|
||||
? FileWatcherEventKind.Deleted
|
||||
: FileWatcherEventKind.Changed;
|
||||
|
||||
if (eventKind === FileWatcherEventKind.Changed && +curr.mtime <= +prev.mtime) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback(fileName, eventKind);
|
||||
}
|
||||
}
|
||||
|
||||
function fsWatchDirectory(directoryName: string, callback: (eventName: string, relativeFileName: string) => void, recursive?: boolean): FileWatcher {
|
||||
// Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows
|
||||
// (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643)
|
||||
let options: any;
|
||||
let watcher = !directoryExists(directoryName) ?
|
||||
watchMissingDirectory() :
|
||||
watchPresentDirectory();
|
||||
return {
|
||||
close: () => {
|
||||
watcher.close();
|
||||
}
|
||||
};
|
||||
|
||||
function watchPresentDirectory(): FileWatcher {
|
||||
if (options === undefined) {
|
||||
if (isNode4OrLater && (process.platform === "win32" || process.platform === "darwin")) {
|
||||
options = { persistent: true, recursive: !!recursive };
|
||||
}
|
||||
else {
|
||||
options = { persistent: true };
|
||||
}
|
||||
}
|
||||
|
||||
const dirWatcher = _fs.watch(
|
||||
directoryName,
|
||||
options,
|
||||
callback
|
||||
);
|
||||
dirWatcher.on("error", () => {
|
||||
// Deleting file
|
||||
watcher = watchMissingDirectory();
|
||||
// Call the callback for current directory
|
||||
callback("rename", "");
|
||||
});
|
||||
return dirWatcher;
|
||||
}
|
||||
|
||||
function watchMissingDirectory(): FileWatcher {
|
||||
return fsWatchFile(directoryName, (_fileName, eventKind) => {
|
||||
if (eventKind === FileWatcherEventKind.Created && directoryExists(directoryName)) {
|
||||
watcher.close();
|
||||
watcher = watchPresentDirectory();
|
||||
// Call the callback for current directory
|
||||
// For now it could be callback for the inner directory creation,
|
||||
// but just return current directory, better than current no-op
|
||||
callback("rename", "");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function readFile(fileName: string, _encoding?: string): string | undefined {
|
||||
if (!fileExists(fileName)) {
|
||||
return undefined;
|
||||
@@ -349,7 +425,6 @@ namespace ts {
|
||||
return filter<string>(_fs.readdirSync(path), dir => fileSystemEntryExists(combinePaths(path, dir), FileSystemEntryKind.Directory));
|
||||
}
|
||||
|
||||
const noOpFileWatcher: FileWatcher = { close: noop };
|
||||
const nodeSystem: System = {
|
||||
args: process.argv.slice(2),
|
||||
newLine: _os.EOL,
|
||||
@@ -367,60 +442,21 @@ namespace ts {
|
||||
};
|
||||
}
|
||||
else {
|
||||
_fs.watchFile(fileName, { persistent: true, interval: pollingInterval || 250 }, fileChanged);
|
||||
return {
|
||||
close: () => _fs.unwatchFile(fileName, fileChanged)
|
||||
};
|
||||
}
|
||||
|
||||
function fileChanged(curr: any, prev: any) {
|
||||
const isCurrZero = +curr.mtime === 0;
|
||||
const isPrevZero = +prev.mtime === 0;
|
||||
const created = !isCurrZero && isPrevZero;
|
||||
const deleted = isCurrZero && !isPrevZero;
|
||||
|
||||
const eventKind = created
|
||||
? FileWatcherEventKind.Created
|
||||
: deleted
|
||||
? FileWatcherEventKind.Deleted
|
||||
: FileWatcherEventKind.Changed;
|
||||
|
||||
if (eventKind === FileWatcherEventKind.Changed && +curr.mtime <= +prev.mtime) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback(fileName, eventKind);
|
||||
return fsWatchFile(fileName, callback, pollingInterval);
|
||||
}
|
||||
},
|
||||
watchDirectory: (directoryName, callback, recursive) => {
|
||||
// Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows
|
||||
// (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643)
|
||||
let options: any;
|
||||
if (!directoryExists(directoryName)) {
|
||||
// do nothing if target folder does not exist
|
||||
return noOpFileWatcher;
|
||||
}
|
||||
|
||||
if (isNode4OrLater && (process.platform === "win32" || process.platform === "darwin")) {
|
||||
options = { persistent: true, recursive: !!recursive };
|
||||
}
|
||||
else {
|
||||
options = { persistent: true };
|
||||
}
|
||||
|
||||
return _fs.watch(
|
||||
directoryName,
|
||||
options,
|
||||
(eventName: string, relativeFileName: string) => {
|
||||
// In watchDirectory we only care about adding and removing files (when event name is
|
||||
// "rename"); changes made within files are handled by corresponding fileWatchers (when
|
||||
// event name is "change")
|
||||
if (eventName === "rename") {
|
||||
// When deleting a file, the passed baseFileName is null
|
||||
callback(!relativeFileName ? relativeFileName : normalizePath(combinePaths(directoryName, relativeFileName)));
|
||||
}
|
||||
return fsWatchDirectory(directoryName, (eventName, relativeFileName) => {
|
||||
// In watchDirectory we only care about adding and removing files (when event name is
|
||||
// "rename"); changes made within files are handled by corresponding fileWatchers (when
|
||||
// event name is "change")
|
||||
if (eventName === "rename") {
|
||||
// When deleting a file, the passed baseFileName is null
|
||||
callback(!relativeFileName ? relativeFileName : normalizePath(combinePaths(directoryName, relativeFileName)));
|
||||
}
|
||||
);
|
||||
}, recursive);
|
||||
},
|
||||
resolvePath: path => _path.resolve(path),
|
||||
fileExists,
|
||||
|
||||
@@ -3576,9 +3576,7 @@ namespace ts {
|
||||
|
||||
export function addDirectoryWatcher(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher {
|
||||
const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0;
|
||||
return host.watchDirectory(directory, fileName => {
|
||||
cb(getNormalizedAbsolutePath(fileName, directory));
|
||||
}, recursive);
|
||||
return host.watchDirectory(directory, cb, recursive);
|
||||
}
|
||||
|
||||
export function addDirectoryWatcherWithLogging(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher {
|
||||
|
||||
@@ -245,7 +245,6 @@ namespace ts {
|
||||
|
||||
const sourceFilesCache = createMap<HostFileInfo | string>(); // Cache that stores the source file and version info
|
||||
let missingFilePathsRequestedForRelease: Path[]; // These paths are held temparirly so that we can remove the entry from source file cache if the file is not tracked by missing files
|
||||
let hasInvalidatedResolution: HasInvalidatedResolution; // Passed along to see if source file has invalidated resolutions
|
||||
let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations
|
||||
|
||||
const loggingEnabled = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics;
|
||||
@@ -257,20 +256,47 @@ namespace ts {
|
||||
watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty);
|
||||
const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost;
|
||||
|
||||
const host = configFileName ? createCachedPartialSystem(system) : system;
|
||||
const partialSystem = configFileName ? createCachedPartialSystem(system) : system;
|
||||
if (configFileName) {
|
||||
configFileWatcher = watchFile(system, configFileName, scheduleProgramReload, writeLog);
|
||||
}
|
||||
const currentDirectory = host.getCurrentDirectory();
|
||||
const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames);
|
||||
|
||||
// Cache for the module resolution
|
||||
const resolutionCache = createResolutionCache(
|
||||
fileName => toPath(fileName),
|
||||
() => compilerOptions,
|
||||
const getCurrentDirectory = memoize(() => partialSystem.getCurrentDirectory());
|
||||
const realpath = system.realpath && ((path: string) => system.realpath(path));
|
||||
const getCachedPartialSystem = configFileName && (() => partialSystem as CachedPartialSystem);
|
||||
const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames);
|
||||
let newLine = getNewLineCharacter(compilerOptions, system);
|
||||
|
||||
const compilerHost: CompilerHost & ResolutionCacheHost = {
|
||||
// Members for CompilerHost
|
||||
getSourceFile: getVersionedSourceFile,
|
||||
getSourceFileByPath: getVersionedSourceFileByPath,
|
||||
getDefaultLibLocation,
|
||||
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
|
||||
writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { },
|
||||
getCurrentDirectory,
|
||||
useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames,
|
||||
getCanonicalFileName,
|
||||
getNewLine: () => newLine,
|
||||
fileExists,
|
||||
readFile,
|
||||
trace,
|
||||
directoryExists,
|
||||
getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "",
|
||||
getDirectories,
|
||||
realpath,
|
||||
resolveTypeReferenceDirectives,
|
||||
resolveModuleNames,
|
||||
onReleaseOldSourceFile,
|
||||
// Members for ResolutionCacheHost
|
||||
toPath,
|
||||
getCompilationSettings: () => compilerOptions,
|
||||
watchDirectoryOfFailedLookupLocation,
|
||||
writeLog
|
||||
);
|
||||
getCachedPartialSystem,
|
||||
onInvalidatedResolution: scheduleProgramUpdate
|
||||
};
|
||||
// Cache for the module resolution
|
||||
const resolutionCache = createResolutionCache(compilerHost);
|
||||
|
||||
// There is no extra check needed since we can just rely on the program to decide emit
|
||||
const builder = createBuilder(getCanonicalFileName, getFileEmitOutput, computeHash, _sourceFile => true);
|
||||
@@ -285,14 +311,15 @@ namespace ts {
|
||||
function synchronizeProgram() {
|
||||
writeLog(`Synchronizing program`);
|
||||
|
||||
hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution();
|
||||
if (hasChangedCompilerOptions) {
|
||||
newLine = getNewLineCharacter(compilerOptions, system);
|
||||
}
|
||||
|
||||
const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution();
|
||||
if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the compiler host
|
||||
const compilerHost = createWatchedCompilerHost(compilerOptions);
|
||||
resolutionCache.setModuleResolutionHost(compilerHost);
|
||||
if (hasChangedCompilerOptions && changesAffectModuleResolution(program && program.getCompilerOptions(), compilerOptions)) {
|
||||
resolutionCache.clear();
|
||||
}
|
||||
@@ -300,6 +327,7 @@ namespace ts {
|
||||
beforeCompile(compilerOptions);
|
||||
|
||||
// Compile the program
|
||||
compilerHost.hasInvalidatedResolution = hasInvalidatedResolution;
|
||||
program = createProgram(rootFileNames, compilerOptions, compilerHost, program);
|
||||
builder.onProgramUpdateGraph(program, hasInvalidatedResolution);
|
||||
|
||||
@@ -309,7 +337,7 @@ namespace ts {
|
||||
// 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 that point our watches were updated, hence now we know that these paths are not tracked and need to be removed
|
||||
// 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)) {
|
||||
@@ -319,40 +347,12 @@ namespace ts {
|
||||
missingFilePathsRequestedForRelease = undefined;
|
||||
}
|
||||
|
||||
afterCompile(host, program, builder);
|
||||
afterCompile(partialSystem, program, builder);
|
||||
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes));
|
||||
}
|
||||
|
||||
function createWatchedCompilerHost(options: CompilerOptions): CompilerHost {
|
||||
const newLine = getNewLineCharacter(options, system);
|
||||
const realpath = system.realpath && ((path: string) => system.realpath(path));
|
||||
|
||||
return {
|
||||
getSourceFile: getVersionedSourceFile,
|
||||
getSourceFileByPath: getVersionedSourceFileByPath,
|
||||
getDefaultLibLocation,
|
||||
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
|
||||
writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { },
|
||||
getCurrentDirectory: memoize(() => host.getCurrentDirectory()),
|
||||
useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames,
|
||||
getCanonicalFileName,
|
||||
getNewLine: () => newLine,
|
||||
fileExists,
|
||||
readFile: fileName => system.readFile(fileName),
|
||||
trace: (s: string) => system.write(s + newLine),
|
||||
directoryExists: directoryName => host.directoryExists(directoryName),
|
||||
getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "",
|
||||
getDirectories: (path: string) => host.getDirectories(path),
|
||||
realpath,
|
||||
resolveTypeReferenceDirectives: (typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile),
|
||||
resolveModuleNames: (moduleNames, containingFile) => resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ false),
|
||||
onReleaseOldSourceFile,
|
||||
hasInvalidatedResolution
|
||||
};
|
||||
}
|
||||
|
||||
function toPath(fileName: string) {
|
||||
return ts.toPath(fileName, currentDirectory, getCanonicalFileName);
|
||||
return ts.toPath(fileName, getCurrentDirectory(), getCanonicalFileName);
|
||||
}
|
||||
|
||||
function fileExists(fileName: string) {
|
||||
@@ -362,7 +362,31 @@ namespace ts {
|
||||
return !isString(hostSourceFileInfo);
|
||||
}
|
||||
|
||||
return host.fileExists(fileName);
|
||||
return partialSystem.fileExists(fileName);
|
||||
}
|
||||
|
||||
function directoryExists(directoryName: string) {
|
||||
return partialSystem.directoryExists(directoryName);
|
||||
}
|
||||
|
||||
function readFile(fileName: string) {
|
||||
return system.readFile(fileName);
|
||||
}
|
||||
|
||||
function trace(s: string) {
|
||||
return system.write(s + newLine);
|
||||
}
|
||||
|
||||
function getDirectories(path: string) {
|
||||
return partialSystem.getDirectories(path);
|
||||
}
|
||||
|
||||
function resolveModuleNames(moduleNames: string[], containingFile: string) {
|
||||
return resolutionCache.resolveModuleNames(moduleNames, containingFile, /*logChanges*/ false);
|
||||
}
|
||||
|
||||
function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string) {
|
||||
return resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile);
|
||||
}
|
||||
|
||||
function getDefaultLibLocation(): string {
|
||||
@@ -503,7 +527,7 @@ namespace ts {
|
||||
writeLog(`Reloading config file: ${configFileName}`);
|
||||
needsReload = false;
|
||||
|
||||
const cachedHost = host as CachedPartialSystem;
|
||||
const cachedHost = partialSystem as CachedPartialSystem;
|
||||
cachedHost.clearCache();
|
||||
const configParseResult = parseConfigFile(configFileName, optionsToExtendForConfigFile, cachedHost, reportDiagnostic, reportWatchDiagnostic);
|
||||
rootFileNames = configParseResult.fileNames;
|
||||
@@ -548,26 +572,12 @@ namespace ts {
|
||||
|
||||
function updateCachedSystemWithFile(fileName: string, path: Path, eventKind: FileWatcherEventKind) {
|
||||
if (configFileName) {
|
||||
(host as CachedPartialSystem).addOrDeleteFile(fileName, path, eventKind);
|
||||
(partialSystem as CachedPartialSystem).addOrDeleteFile(fileName, path, eventKind);
|
||||
}
|
||||
}
|
||||
|
||||
function watchDirectoryOfFailedLookupLocation(directory: string) {
|
||||
return watchDirectory(system, directory, onFileAddOrRemoveInDirectoryOfFailedLookup, WatchDirectoryFlags.None, writeLog);
|
||||
}
|
||||
|
||||
function onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolder: string) {
|
||||
const fileOrFolderPath = toPath(fileOrFolder);
|
||||
|
||||
if (configFileName) {
|
||||
// Since the file existance changed, update the sourceFiles cache
|
||||
(host as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath);
|
||||
}
|
||||
|
||||
// If the location results in update to failed lookup, schedule program update
|
||||
if (resolutionCache.onFileAddOrRemoveInDirectoryOfFailedLookup(fileOrFolderPath)) {
|
||||
scheduleProgramUpdate();
|
||||
}
|
||||
function watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback) {
|
||||
return watchDirectory(system, directory, cb, WatchDirectoryFlags.None, writeLog);
|
||||
}
|
||||
|
||||
function watchMissingFilePath(missingFilePath: Path) {
|
||||
@@ -593,42 +603,45 @@ namespace ts {
|
||||
updateWatchingWildcardDirectories(
|
||||
watchedWildcardDirectories || (watchedWildcardDirectories = createMap()),
|
||||
createMapFromTemplate(configFileWildCardDirectories),
|
||||
watchWildCardDirectory
|
||||
watchWildcardDirectory
|
||||
);
|
||||
}
|
||||
|
||||
function watchWildCardDirectory(directory: string, flags: WatchDirectoryFlags) {
|
||||
return watchDirectory(system, directory, onFileAddOrRemoveInWatchedDirectory, flags, writeLog);
|
||||
}
|
||||
function watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags) {
|
||||
return watchDirectory(
|
||||
system,
|
||||
directory,
|
||||
fileOrFolder => {
|
||||
Debug.assert(!!configFileName);
|
||||
|
||||
function onFileAddOrRemoveInWatchedDirectory(fileOrFolder: string) {
|
||||
Debug.assert(!!configFileName);
|
||||
const fileOrFolderPath = toPath(fileOrFolder);
|
||||
|
||||
const fileOrFolderPath = toPath(fileOrFolder);
|
||||
// Since the file existance changed, update the sourceFiles cache
|
||||
(partialSystem as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath);
|
||||
removeSourceFile(fileOrFolderPath);
|
||||
|
||||
// Since the file existance changed, update the sourceFiles cache
|
||||
(host as CachedPartialSystem).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath);
|
||||
removeSourceFile(fileOrFolderPath);
|
||||
// If the the added or created file or folder is not supported file name, ignore the file
|
||||
// But when watched directory is added/removed, we need to reload the file list
|
||||
if (fileOrFolderPath !== directory && !isSupportedSourceFileName(fileOrFolder, compilerOptions)) {
|
||||
writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrFolder}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// If a change was made inside "folder/file", node will trigger the callback twice:
|
||||
// one with the fileName being "folder/file", and the other one with "folder".
|
||||
// We don't respond to the second one.
|
||||
if (fileOrFolder && !isSupportedSourceFileName(fileOrFolder, compilerOptions)) {
|
||||
writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrFolder}`);
|
||||
return;
|
||||
}
|
||||
// Reload is pending, do the reload
|
||||
if (!needsReload) {
|
||||
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, partialSystem);
|
||||
if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) {
|
||||
reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName));
|
||||
}
|
||||
rootFileNames = result.fileNames;
|
||||
|
||||
// Reload is pending, do the reload
|
||||
if (!needsReload) {
|
||||
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), compilerOptions, host);
|
||||
if (!configFileSpecs.filesSpecs && result.fileNames.length === 0) {
|
||||
reportDiagnostic(getErrorForNoInputFiles(configFileSpecs, configFileName));
|
||||
}
|
||||
rootFileNames = result.fileNames;
|
||||
|
||||
// Schedule Update the program
|
||||
scheduleProgramUpdate();
|
||||
}
|
||||
// Schedule Update the program
|
||||
scheduleProgramUpdate();
|
||||
}
|
||||
},
|
||||
flags,
|
||||
writeLog
|
||||
);
|
||||
}
|
||||
|
||||
function computeHash(data: string) {
|
||||
|
||||
Reference in New Issue
Block a user