mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-21 04:17:02 -06:00
PR feedback
This commit is contained in:
parent
38f3a2b700
commit
68d360585a
@ -43,10 +43,23 @@ namespace ts {
|
||||
}
|
||||
|
||||
interface EmitHandler {
|
||||
addScriptInfo(program: Program, sourceFile: SourceFile): void;
|
||||
removeScriptInfo(path: Path): void;
|
||||
updateScriptInfo(program: Program, sourceFile: SourceFile): void;
|
||||
updaterScriptInfoWithSameVersion(program: Program, sourceFile: SourceFile): boolean;
|
||||
/**
|
||||
* Called when sourceFile is added to the program
|
||||
*/
|
||||
onAddSourceFile(program: Program, sourceFile: SourceFile): void;
|
||||
/**
|
||||
* Called when sourceFile is removed from the program
|
||||
*/
|
||||
onRemoveSourceFile(path: Path): void;
|
||||
/**
|
||||
* Called when sourceFile is changed
|
||||
*/
|
||||
onUpdateSourceFile(program: Program, sourceFile: SourceFile): void;
|
||||
/**
|
||||
* Called when source file has not changed but has some of the resolutions invalidated
|
||||
* If returned true, builder will mark the file as changed (noting that something associated with file has changed)
|
||||
*/
|
||||
onUpdateSourceFileWithSameVersion(program: Program, sourceFile: SourceFile): boolean;
|
||||
/**
|
||||
* Gets the files affected by the script info which has updated shape from the known one
|
||||
*/
|
||||
@ -135,23 +148,23 @@ namespace ts {
|
||||
|
||||
function addNewFileInfo(program: Program, sourceFile: SourceFile): FileInfo {
|
||||
registerChangedFile(sourceFile.path, sourceFile.fileName);
|
||||
emitHandler.addScriptInfo(program, sourceFile);
|
||||
emitHandler.onAddSourceFile(program, sourceFile);
|
||||
return { fileName: sourceFile.fileName, version: sourceFile.version, signature: undefined };
|
||||
}
|
||||
|
||||
function removeExistingFileInfo(existingFileInfo: FileInfo, path: Path) {
|
||||
registerChangedFile(path, existingFileInfo.fileName);
|
||||
emitHandler.removeScriptInfo(path);
|
||||
emitHandler.onRemoveSourceFile(path);
|
||||
}
|
||||
|
||||
function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile, hasInvalidatedResolution: HasInvalidatedResolution) {
|
||||
if (existingInfo.version !== sourceFile.version) {
|
||||
registerChangedFile(sourceFile.path, sourceFile.fileName);
|
||||
existingInfo.version = sourceFile.version;
|
||||
emitHandler.updateScriptInfo(program, sourceFile);
|
||||
emitHandler.onUpdateSourceFile(program, sourceFile);
|
||||
}
|
||||
else if (hasInvalidatedResolution(sourceFile.path) &&
|
||||
emitHandler.updaterScriptInfoWithSameVersion(program, sourceFile)) {
|
||||
emitHandler.onUpdateSourceFileWithSameVersion(program, sourceFile)) {
|
||||
registerChangedFile(sourceFile.path, sourceFile.fileName);
|
||||
}
|
||||
}
|
||||
@ -388,10 +401,10 @@ namespace ts {
|
||||
|
||||
function getNonModuleEmitHandler(): EmitHandler {
|
||||
return {
|
||||
addScriptInfo: noop,
|
||||
removeScriptInfo: noop,
|
||||
updateScriptInfo: noop,
|
||||
updaterScriptInfoWithSameVersion: returnFalse,
|
||||
onAddSourceFile: noop,
|
||||
onRemoveSourceFile: noop,
|
||||
onUpdateSourceFile: noop,
|
||||
onUpdateSourceFileWithSameVersion: returnFalse,
|
||||
getFilesAffectedByUpdatedShape
|
||||
};
|
||||
|
||||
@ -409,17 +422,17 @@ namespace ts {
|
||||
function getModuleEmitHandler(): EmitHandler {
|
||||
const references = createMap<Map<true>>();
|
||||
return {
|
||||
addScriptInfo: setReferences,
|
||||
removeScriptInfo,
|
||||
updateScriptInfo: updateReferences,
|
||||
updaterScriptInfoWithSameVersion: updateReferencesTrackingChangedReferences,
|
||||
onAddSourceFile: setReferences,
|
||||
onRemoveSourceFile,
|
||||
onUpdateSourceFile: updateReferences,
|
||||
onUpdateSourceFileWithSameVersion: updateReferencesTrackingChangedReferences,
|
||||
getFilesAffectedByUpdatedShape
|
||||
};
|
||||
|
||||
function setReferences(program: Program, sourceFile: SourceFile) {
|
||||
const newReferences = getReferencedFiles(program, sourceFile);
|
||||
if (newReferences) {
|
||||
references.set(sourceFile.path, getReferencedFiles(program, sourceFile));
|
||||
references.set(sourceFile.path, newReferences);
|
||||
}
|
||||
}
|
||||
|
||||
@ -452,7 +465,7 @@ namespace ts {
|
||||
!!oldReferences.size;
|
||||
}
|
||||
|
||||
function removeScriptInfo(removedFilePath: Path) {
|
||||
function onRemoveSourceFile(removedFilePath: Path) {
|
||||
// Remove existing references
|
||||
references.forEach((referencesInFile, filePath) => {
|
||||
if (referencesInFile.has(removedFilePath)) {
|
||||
|
||||
@ -1683,7 +1683,7 @@ namespace ts {
|
||||
return undefined;
|
||||
}
|
||||
let extendedConfigPath = toPath(extendedConfig, basePath, getCanonicalFileName);
|
||||
if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, ".json")) {
|
||||
if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, Extension.Json)) {
|
||||
extendedConfigPath = `${extendedConfigPath}.json` as Path;
|
||||
if (!host.fileExists(extendedConfigPath)) {
|
||||
errors.push(createDiagnostic(Diagnostics.File_0_does_not_exist, extendedConfig));
|
||||
|
||||
@ -2262,7 +2262,7 @@ namespace ts {
|
||||
return ScriptKind.TS;
|
||||
case Extension.Tsx:
|
||||
return ScriptKind.TSX;
|
||||
case ".json":
|
||||
case Extension.Json:
|
||||
return ScriptKind.JSON;
|
||||
default:
|
||||
return ScriptKind.Unknown;
|
||||
@ -2669,7 +2669,7 @@ namespace ts {
|
||||
export function assertTypeIsNever(_: never): void { }
|
||||
|
||||
export interface CachedDirectoryStructureHost extends DirectoryStructureHost {
|
||||
addOrDeleteFileOrFolder(fileOrFolder: string, fileOrFolderPath: Path): void;
|
||||
addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path): void;
|
||||
addOrDeleteFile(fileName: string, filePath: Path, eventKind: FileWatcherEventKind): void;
|
||||
clearCache(): void;
|
||||
}
|
||||
@ -2695,7 +2695,7 @@ namespace ts {
|
||||
getCurrentDirectory,
|
||||
getDirectories,
|
||||
readDirectory,
|
||||
addOrDeleteFileOrFolder,
|
||||
addOrDeleteFileOrDirectory,
|
||||
addOrDeleteFile,
|
||||
clearCache,
|
||||
exit: code => host.exit(code)
|
||||
@ -2824,23 +2824,23 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function addOrDeleteFileOrFolder(fileOrFolder: string, fileOrFolderPath: Path) {
|
||||
const existingResult = getCachedFileSystemEntries(fileOrFolderPath);
|
||||
function addOrDeleteFileOrDirectory(fileOrDirectory: string, fileOrDirectoryPath: Path) {
|
||||
const existingResult = getCachedFileSystemEntries(fileOrDirectoryPath);
|
||||
if (existingResult) {
|
||||
// This was a folder already present, remove it if this doesnt exist any more
|
||||
if (!host.directoryExists(fileOrFolder)) {
|
||||
cachedReadDirectoryResult.delete(fileOrFolderPath);
|
||||
if (!host.directoryExists(fileOrDirectory)) {
|
||||
cachedReadDirectoryResult.delete(fileOrDirectoryPath);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// This was earlier a file (hence not in cached directory contents)
|
||||
// or we never cached the directory containing it
|
||||
const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrFolderPath);
|
||||
const parentResult = getCachedFileSystemEntriesForBaseDir(fileOrDirectoryPath);
|
||||
if (parentResult) {
|
||||
const baseName = getBaseNameOfFileName(fileOrFolder);
|
||||
const baseName = getBaseNameOfFileName(fileOrDirectory);
|
||||
if (parentResult) {
|
||||
updateFilesOfFileSystemEntry(parentResult, baseName, host.fileExists(fileOrFolderPath));
|
||||
updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrFolderPath));
|
||||
updateFilesOfFileSystemEntry(parentResult, baseName, host.fileExists(fileOrDirectoryPath));
|
||||
updateFileSystemEntry(parentResult.directories, baseName, host.directoryExists(fileOrDirectoryPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,6 +395,9 @@ namespace ts {
|
||||
allDiagnostics?: Diagnostic[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if program structure is upto date or needs to be recreated
|
||||
*/
|
||||
export function isProgramUptoDate(
|
||||
program: Program | undefined,
|
||||
rootFileNames: string[],
|
||||
@ -402,10 +405,10 @@ namespace ts {
|
||||
getSourceVersion: (path: Path) => string,
|
||||
fileExists: (fileName: string) => boolean,
|
||||
hasInvalidatedResolution: HasInvalidatedResolution,
|
||||
hasChangedAutomaticTypeDirectiveNames: () => boolean,
|
||||
hasChangedAutomaticTypeDirectiveNames: boolean,
|
||||
): boolean {
|
||||
// If we haven't create a program yet or has changed automatic type directives, then it is not up-to-date
|
||||
if (!program || hasChangedAutomaticTypeDirectiveNames()) {
|
||||
if (!program || hasChangedAutomaticTypeDirectiveNames) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -416,7 +419,7 @@ namespace ts {
|
||||
|
||||
// If any file is not up-to-date, then the whole program is not up-to-date
|
||||
if (program.getSourceFiles().some(sourceFileNotUptoDate)) {
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// If any of the missing file paths are now created
|
||||
@ -464,78 +467,6 @@ namespace ts {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the existing missing file watches with the new set of missing files after new program is created
|
||||
*/
|
||||
export function updateMissingFilePathsWatch(
|
||||
program: Program,
|
||||
missingFileWatches: Map<FileWatcher>,
|
||||
createMissingFileWatch: (missingFilePath: Path) => FileWatcher,
|
||||
) {
|
||||
const missingFilePaths = program.getMissingFilePaths();
|
||||
const newMissingFilePathMap = arrayToSet(missingFilePaths);
|
||||
// Update the missing file paths watcher
|
||||
mutateMap(
|
||||
missingFileWatches,
|
||||
newMissingFilePathMap,
|
||||
{
|
||||
// Watch the missing files
|
||||
createNewValue: createMissingFileWatch,
|
||||
// Files that are no longer missing (e.g. because they are no longer required)
|
||||
// should no longer be watched.
|
||||
onDeleteValue: closeFileWatcher
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export interface WildcardDirectoryWatcher {
|
||||
watcher: FileWatcher;
|
||||
flags: WatchDirectoryFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the existing wild card directory watches with the new set of wild card directories from the config file
|
||||
* after new program is created because the config file was reloaded or program was created first time from the config file
|
||||
* Note that there is no need to call this function when the program is updated with additional files without reloading config files,
|
||||
* as wildcard directories wont change unless reloading config file
|
||||
*/
|
||||
export function updateWatchingWildcardDirectories(
|
||||
existingWatchedForWildcards: Map<WildcardDirectoryWatcher>,
|
||||
wildcardDirectories: Map<WatchDirectoryFlags>,
|
||||
watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher
|
||||
) {
|
||||
mutateMap(
|
||||
existingWatchedForWildcards,
|
||||
wildcardDirectories,
|
||||
{
|
||||
// Create new watch and recursive info
|
||||
createNewValue: createWildcardDirectoryWatcher,
|
||||
// Close existing watch thats not needed any more
|
||||
onDeleteValue: closeFileWatcherOf,
|
||||
// Close existing watch that doesnt match in the flags
|
||||
onExistingValue: updateWildcardDirectoryWatcher
|
||||
}
|
||||
);
|
||||
|
||||
function createWildcardDirectoryWatcher(directory: string, flags: WatchDirectoryFlags): WildcardDirectoryWatcher {
|
||||
// Create new watch and recursive info
|
||||
return {
|
||||
watcher: watchDirectory(directory, flags),
|
||||
flags
|
||||
};
|
||||
}
|
||||
|
||||
function updateWildcardDirectoryWatcher(existingWatcher: WildcardDirectoryWatcher, flags: WatchDirectoryFlags, directory: string) {
|
||||
// Watcher needs to be updated if the recursive flags dont match
|
||||
if (existingWatcher.flags === flags) {
|
||||
return;
|
||||
}
|
||||
|
||||
existingWatcher.watcher.close();
|
||||
existingWatchedForWildcards.set(directory, createWildcardDirectoryWatcher(directory, flags));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new 'Program' instance. A Program is an immutable collection of 'SourceFile's and a 'CompilerOptions'
|
||||
* that represent a compilation unit.
|
||||
@ -599,7 +530,6 @@ namespace ts {
|
||||
let moduleResolutionCache: ModuleResolutionCache;
|
||||
let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string, reusedNames?: string[]) => ResolvedModuleFull[];
|
||||
const hasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse;
|
||||
const hasChangedAutomaticTypeDirectiveNames = host.hasChangedAutomaticTypeDirectiveNames && host.hasChangedAutomaticTypeDirectiveNames.bind(host) || returnFalse;
|
||||
if (host.resolveModuleNames) {
|
||||
resolveModuleNamesWorker = (moduleNames, containingFile, reusedNames) => host.resolveModuleNames(checkAllDefined(moduleNames), containingFile, reusedNames).map(resolved => {
|
||||
// An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName.
|
||||
@ -1105,7 +1035,7 @@ namespace ts {
|
||||
return oldProgram.structureIsReused;
|
||||
}
|
||||
|
||||
if (hasChangedAutomaticTypeDirectiveNames()) {
|
||||
if (host.hasChangedAutomaticTypeDirectiveNames) {
|
||||
return oldProgram.structureIsReused = StructureIsReused.SafeModules;
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
/// <reference path="types.ts"/>
|
||||
/// <reference path="core.ts"/>
|
||||
/// <reference path="watchUtilities.ts"/>
|
||||
|
||||
/*@internal*/
|
||||
namespace ts {
|
||||
@ -18,8 +19,6 @@ namespace ts {
|
||||
startCachingPerDirectoryResolution(): void;
|
||||
finishCachingPerDirectoryResolution(): void;
|
||||
|
||||
setRootDirectory(dir: string): void;
|
||||
|
||||
updateTypeRootsWatch(): void;
|
||||
closeTypeRootsWatch(): void;
|
||||
|
||||
@ -35,6 +34,12 @@ namespace ts {
|
||||
resolvedFileName: string | undefined;
|
||||
}
|
||||
|
||||
interface ResolvedModuleWithFailedLookupLocations extends ts.ResolvedModuleWithFailedLookupLocations, ResolutionWithFailedLookupLocations {
|
||||
}
|
||||
|
||||
interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations extends ts.ResolvedTypeReferenceDirectiveWithFailedLookupLocations, ResolutionWithFailedLookupLocations {
|
||||
}
|
||||
|
||||
export interface ResolutionCacheHost extends ModuleResolutionHost {
|
||||
toPath(fileName: string): Path;
|
||||
getCompilationSettings(): CompilerOptions;
|
||||
@ -67,7 +72,7 @@ namespace ts {
|
||||
(resolution: T): R;
|
||||
}
|
||||
|
||||
export function createResolutionCache(resolutionHost: ResolutionCacheHost): ResolutionCache {
|
||||
export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string): ResolutionCache {
|
||||
let filesWithChangedSetOfUnresolvedImports: Path[] | undefined;
|
||||
let filesWithInvalidatedResolutions: Map<true> | undefined;
|
||||
let allFilesHaveInvalidatedResolution = false;
|
||||
@ -83,12 +88,18 @@ namespace ts {
|
||||
|
||||
const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory());
|
||||
|
||||
/**
|
||||
* These are the extensions that failed lookup files will have by default,
|
||||
* any other extension of failed lookup will be store that path in custom failed lookup path
|
||||
* This helps in not having to comb through all resolutions when files are added/removed
|
||||
* Note that .d.ts file also has .d.ts extension hence will be part of default extensions
|
||||
*/
|
||||
const failedLookupDefaultExtensions = [Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json];
|
||||
const customFailedLookupPaths = createMap<number>();
|
||||
|
||||
const directoryWatchesOfFailedLookups = createMap<DirectoryWatchesOfFailedLookup>();
|
||||
let rootDir: string;
|
||||
let rootPath: Path;
|
||||
const rootDir = rootDirForResolution && removeTrailingDirectorySeparator(getNormalizedAbsolutePath(rootDirForResolution, getCurrentDirectory()));
|
||||
const rootPath = rootDir && resolutionHost.toPath(rootDir);
|
||||
|
||||
// TypeRoot watches for the types that get added as part of getAutomaticTypeDirectiveNames
|
||||
const typeRootsWatches = createMap<FileWatcher>();
|
||||
@ -103,7 +114,6 @@ namespace ts {
|
||||
removeResolutionsOfFile,
|
||||
invalidateResolutionOfFile,
|
||||
createHasInvalidatedResolution,
|
||||
setRootDirectory,
|
||||
updateTypeRootsWatch,
|
||||
closeTypeRootsWatch,
|
||||
clear
|
||||
@ -117,12 +127,6 @@ namespace ts {
|
||||
return resolution.resolvedTypeReferenceDirective;
|
||||
}
|
||||
|
||||
function setRootDirectory(dir: string) {
|
||||
Debug.assert(!resolvedModuleNames.size && !resolvedTypeReferenceDirectives.size && !directoryWatchesOfFailedLookups.size);
|
||||
rootDir = removeTrailingDirectorySeparator(getNormalizedAbsolutePath(dir, getCurrentDirectory()));
|
||||
rootPath = resolutionHost.toPath(rootDir);
|
||||
}
|
||||
|
||||
function isInDirectoryPath(dir: Path, file: Path) {
|
||||
if (dir === undefined || file.length <= dir.length) {
|
||||
return false;
|
||||
@ -238,9 +242,17 @@ namespace ts {
|
||||
perDirectoryResolution.set(name, resolution);
|
||||
}
|
||||
resolutionsInFile.set(name, resolution);
|
||||
const diffIndex = existingResolution && existingResolution.failedLookupLocations && resolution.failedLookupLocations && findDiffIndex(resolution.failedLookupLocations, existingResolution.failedLookupLocations);
|
||||
watchFailedLookupLocationOfResolution(resolution, diffIndex || 0);
|
||||
stopWatchFailedLookupLocationOfResolutionFrom(existingResolution, diffIndex || 0);
|
||||
if (resolution.failedLookupLocations) {
|
||||
if (existingResolution && existingResolution.failedLookupLocations) {
|
||||
watchAndStopWatchDiffFailedLookupLocations(resolution, existingResolution);
|
||||
}
|
||||
else {
|
||||
watchFailedLookupLocationOfResolution(resolution, 0);
|
||||
}
|
||||
}
|
||||
else if (existingResolution) {
|
||||
stopWatchFailedLookupLocationOfResolution(existingResolution);
|
||||
}
|
||||
if (logChanges && filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) {
|
||||
filesWithChangedSetOfUnresolvedImports.push(path);
|
||||
// reset log changes to avoid recording the same file multiple times
|
||||
@ -344,68 +356,91 @@ namespace ts {
|
||||
return fileExtensionIsOneOf(path, failedLookupDefaultExtensions);
|
||||
}
|
||||
|
||||
function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations, startIndex?: number) {
|
||||
if (resolution && resolution.failedLookupLocations) {
|
||||
for (let i = startIndex || 0; i < resolution.failedLookupLocations.length; i++) {
|
||||
const failedLookupLocation = resolution.failedLookupLocations[i];
|
||||
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
|
||||
// If the failed lookup location path is not one of the supported extensions,
|
||||
// store it in the custom path
|
||||
if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) {
|
||||
const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0;
|
||||
customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1);
|
||||
}
|
||||
const { dir, dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
|
||||
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
|
||||
if (dirWatcher) {
|
||||
dirWatcher.refCount++;
|
||||
}
|
||||
else {
|
||||
directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 });
|
||||
}
|
||||
function watchAndStopWatchDiffFailedLookupLocations(resolution: ResolutionWithFailedLookupLocations, existingResolution: ResolutionWithFailedLookupLocations) {
|
||||
const failedLookupLocations = resolution.failedLookupLocations;
|
||||
const existingFailedLookupLocations = existingResolution.failedLookupLocations;
|
||||
for (let index = 0; index < failedLookupLocations.length; index++) {
|
||||
if (index === existingFailedLookupLocations.length) {
|
||||
// Additional failed lookup locations, watch from this index
|
||||
watchFailedLookupLocationOfResolution(resolution, index);
|
||||
return;
|
||||
}
|
||||
else if (failedLookupLocations[index] !== existingFailedLookupLocations[index]) {
|
||||
// Different failed lookup locations,
|
||||
// Watch new resolution failed lookup locations from this index and
|
||||
// stop watching existing resolutions from this index
|
||||
watchFailedLookupLocationOfResolution(resolution, index);
|
||||
stopWatchFailedLookupLocationOfResolutionFrom(existingResolution, index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// All new failed lookup locations are already watched (and are same),
|
||||
// Stop watching failed lookup locations of existing resolution after failed lookup locations length
|
||||
stopWatchFailedLookupLocationOfResolutionFrom(existingResolution, failedLookupLocations.length);
|
||||
}
|
||||
|
||||
function watchFailedLookupLocationOfResolution({ failedLookupLocations }: ResolutionWithFailedLookupLocations, startIndex: number) {
|
||||
for (let i = startIndex; i < failedLookupLocations.length; i++) {
|
||||
const failedLookupLocation = failedLookupLocations[i];
|
||||
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
|
||||
// If the failed lookup location path is not one of the supported extensions,
|
||||
// store it in the custom path
|
||||
if (!isPathWithDefaultFailedLookupExtension(failedLookupLocationPath)) {
|
||||
const refCount = customFailedLookupPaths.get(failedLookupLocationPath) || 0;
|
||||
customFailedLookupPaths.set(failedLookupLocationPath, refCount + 1);
|
||||
}
|
||||
const { dir, dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
|
||||
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
|
||||
if (dirWatcher) {
|
||||
dirWatcher.refCount++;
|
||||
}
|
||||
else {
|
||||
directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function stopWatchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) {
|
||||
stopWatchFailedLookupLocationOfResolutionFrom(resolution, 0);
|
||||
if (resolution.failedLookupLocations) {
|
||||
stopWatchFailedLookupLocationOfResolutionFrom(resolution, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function stopWatchFailedLookupLocationOfResolutionFrom(resolution: ResolutionWithFailedLookupLocations, startIndex: number) {
|
||||
if (resolution && resolution.failedLookupLocations) {
|
||||
for (let i = startIndex; i < resolution.failedLookupLocations.length; i++) {
|
||||
const failedLookupLocation = resolution.failedLookupLocations[i];
|
||||
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
|
||||
const refCount = customFailedLookupPaths.get(failedLookupLocationPath);
|
||||
if (refCount) {
|
||||
if (refCount === 1) {
|
||||
customFailedLookupPaths.delete(failedLookupLocationPath);
|
||||
}
|
||||
else {
|
||||
customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1);
|
||||
}
|
||||
function stopWatchFailedLookupLocationOfResolutionFrom({ failedLookupLocations }: ResolutionWithFailedLookupLocations, startIndex: number) {
|
||||
for (let i = startIndex; i < failedLookupLocations.length; i++) {
|
||||
const failedLookupLocation = failedLookupLocations[i];
|
||||
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
|
||||
const refCount = customFailedLookupPaths.get(failedLookupLocationPath);
|
||||
if (refCount) {
|
||||
if (refCount === 1) {
|
||||
customFailedLookupPaths.delete(failedLookupLocationPath);
|
||||
}
|
||||
else {
|
||||
Debug.assert(refCount > 1);
|
||||
customFailedLookupPaths.set(failedLookupLocationPath, refCount - 1);
|
||||
}
|
||||
const { dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
|
||||
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
|
||||
// Do not close the watcher yet since it might be needed by other failed lookup locations.
|
||||
dirWatcher.refCount--;
|
||||
}
|
||||
const { dirPath } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
|
||||
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
|
||||
// Do not close the watcher yet since it might be needed by other failed lookup locations.
|
||||
dirWatcher.refCount--;
|
||||
}
|
||||
}
|
||||
|
||||
function createDirectoryWatcher(directory: string, dirPath: Path) {
|
||||
return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrFolder => {
|
||||
const fileOrFolderPath = resolutionHost.toPath(fileOrFolder);
|
||||
return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => {
|
||||
const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory);
|
||||
if (resolutionHost.getCachedDirectoryStructureHost) {
|
||||
// Since the file existance changed, update the sourceFiles cache
|
||||
resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath);
|
||||
resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
|
||||
}
|
||||
|
||||
// If the files are added to project root or node_modules directory, always run through the invalidation process
|
||||
// Otherwise run through invalidation only if adding to the immediate directory
|
||||
if (!allFilesHaveInvalidatedResolution &&
|
||||
dirPath === rootPath || isNodeModulesDirectory(dirPath) || getDirectoryPath(fileOrFolderPath) === dirPath) {
|
||||
if (invalidateResolutionOfFailedLookupLocation(fileOrFolderPath, dirPath === fileOrFolderPath)) {
|
||||
dirPath === rootPath || isNodeModulesDirectory(dirPath) || getDirectoryPath(fileOrDirectoryPath) === dirPath) {
|
||||
if (invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath)) {
|
||||
resolutionHost.onInvalidatedResolution();
|
||||
}
|
||||
}
|
||||
@ -482,21 +517,21 @@ namespace ts {
|
||||
);
|
||||
}
|
||||
|
||||
function invalidateResolutionOfFailedLookupLocation(fileOrFolderPath: Path, isCreatingWatchedDirectory: boolean) {
|
||||
function invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) {
|
||||
let isChangedFailedLookupLocation: (location: string) => boolean;
|
||||
if (isCreatingWatchedDirectory) {
|
||||
// Watching directory is created
|
||||
// Invalidate any resolution has failed lookup in this directory
|
||||
isChangedFailedLookupLocation = location => isInDirectoryPath(fileOrFolderPath, resolutionHost.toPath(location));
|
||||
isChangedFailedLookupLocation = location => isInDirectoryPath(fileOrDirectoryPath, resolutionHost.toPath(location));
|
||||
}
|
||||
else {
|
||||
// Some file or folder in the watching directory is created
|
||||
// Some file or directory in the watching directory is created
|
||||
// Return early if it does not have any of the watching extension or not the custom failed lookup path
|
||||
if (!isPathWithDefaultFailedLookupExtension(fileOrFolderPath) && !customFailedLookupPaths.has(fileOrFolderPath)) {
|
||||
if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) {
|
||||
return false;
|
||||
}
|
||||
// Resolution need to be invalidated if failed lookup location is same as the file or folder getting created
|
||||
isChangedFailedLookupLocation = location => resolutionHost.toPath(location) === fileOrFolderPath;
|
||||
// Resolution need to be invalidated if failed lookup location is same as the file or directory getting created
|
||||
isChangedFailedLookupLocation = location => resolutionHost.toPath(location) === fileOrDirectoryPath;
|
||||
}
|
||||
const hasChangedFailedLookupLocation = (resolution: ResolutionWithFailedLookupLocations) => some(resolution.failedLookupLocations, isChangedFailedLookupLocation);
|
||||
const invalidatedFilesCount = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size;
|
||||
@ -513,11 +548,11 @@ namespace ts {
|
||||
|
||||
function createTypeRootsWatch(_typeRootPath: string, typeRoot: string): FileWatcher {
|
||||
// Create new watch and recursive info
|
||||
return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrFolder => {
|
||||
const fileOrFolderPath = resolutionHost.toPath(fileOrFolder);
|
||||
return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrDirectory => {
|
||||
const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory);
|
||||
if (resolutionHost.getCachedDirectoryStructureHost) {
|
||||
// Since the file existance changed, update the sourceFiles cache
|
||||
resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath);
|
||||
resolutionHost.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
|
||||
}
|
||||
|
||||
// For now just recompile
|
||||
|
||||
@ -78,8 +78,7 @@ namespace ts {
|
||||
close(): void;
|
||||
}
|
||||
|
||||
export interface DirectoryWatcher extends FileWatcher {
|
||||
directoryName: string;
|
||||
interface DirectoryWatcher extends FileWatcher {
|
||||
referenceCount: number;
|
||||
}
|
||||
|
||||
@ -187,7 +186,7 @@ namespace ts {
|
||||
fileWatcherCallbacks.remove(filePath, callback);
|
||||
}
|
||||
|
||||
function fileEventHandler(eventName: string, relativeFileName: string, baseDirPath: string) {
|
||||
function fileEventHandler(eventName: string, relativeFileName: string | undefined, baseDirPath: string) {
|
||||
// When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined"
|
||||
const fileName = !isString(relativeFileName)
|
||||
? undefined
|
||||
@ -255,19 +254,25 @@ namespace ts {
|
||||
}
|
||||
|
||||
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;
|
||||
/** Watcher for the directory depending on whether it is missing or present */
|
||||
let watcher = !directoryExists(directoryName) ?
|
||||
watchMissingDirectory() :
|
||||
watchPresentDirectory();
|
||||
return {
|
||||
close: () => {
|
||||
// Close the watcher (either existing directory watcher or missing directory watcher)
|
||||
watcher.close();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Watch the directory that is currently present
|
||||
* and when the watched directory is deleted, switch to missing directory watcher
|
||||
*/
|
||||
function watchPresentDirectory(): 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)
|
||||
if (options === undefined) {
|
||||
if (isNode4OrLater && (process.platform === "win32" || process.platform === "darwin")) {
|
||||
options = { persistent: true, recursive: !!recursive };
|
||||
@ -283,14 +288,20 @@ namespace ts {
|
||||
callback
|
||||
);
|
||||
dirWatcher.on("error", () => {
|
||||
// Deleting file
|
||||
watcher = watchMissingDirectory();
|
||||
// Call the callback for current directory
|
||||
callback("rename", "");
|
||||
if (!directoryExists(directoryName)) {
|
||||
// Deleting directory
|
||||
watcher = watchMissingDirectory();
|
||||
// Call the callback for current directory
|
||||
callback("rename", "");
|
||||
}
|
||||
});
|
||||
return dirWatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch the directory that is missing
|
||||
* and switch to existing directory when the directory is created
|
||||
*/
|
||||
function watchMissingDirectory(): FileWatcher {
|
||||
return fsWatchFile(directoryName, (_fileName, eventKind) => {
|
||||
if (eventKind === FileWatcherEventKind.Created && directoryExists(directoryName)) {
|
||||
|
||||
@ -36,6 +36,7 @@
|
||||
"sourcemap.ts",
|
||||
"declarationEmitter.ts",
|
||||
"emitter.ts",
|
||||
"watchUtilities.ts",
|
||||
"program.ts",
|
||||
"builder.ts",
|
||||
"resolutionCache.ts",
|
||||
|
||||
@ -4099,8 +4099,6 @@ namespace ts {
|
||||
readonly resolvedModule: ResolvedModuleFull | undefined;
|
||||
/* @internal */
|
||||
readonly failedLookupLocations: ReadonlyArray<string>;
|
||||
/*@internal*/
|
||||
isInvalidated?: boolean;
|
||||
}
|
||||
|
||||
export interface ResolvedTypeReferenceDirective {
|
||||
@ -4114,8 +4112,6 @@ namespace ts {
|
||||
export interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations {
|
||||
readonly resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective;
|
||||
readonly failedLookupLocations: ReadonlyArray<string>;
|
||||
/*@internal*/
|
||||
isInvalidated?: boolean;
|
||||
}
|
||||
|
||||
export interface HasInvalidatedResolution {
|
||||
@ -4150,7 +4146,7 @@ namespace ts {
|
||||
getEnvironmentVariable?(name: string): string;
|
||||
onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions): void;
|
||||
hasInvalidatedResolution?: HasInvalidatedResolution;
|
||||
hasChangedAutomaticTypeDirectiveNames?(): boolean;
|
||||
hasChangedAutomaticTypeDirectiveNames?: boolean;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
|
||||
@ -3521,7 +3521,7 @@ namespace ts {
|
||||
/**
|
||||
* clears already present map by calling onDeleteExistingValue callback before deleting that key/value
|
||||
*/
|
||||
export function clearMap<T>(map: Map<T>, onDeleteValue: (existingValue: T, key: string) => void) {
|
||||
export function clearMap<T>(map: Map<T>, onDeleteValue: (valueInMap: T, key: string) => void) {
|
||||
// Remove all
|
||||
map.forEach(onDeleteValue);
|
||||
map.clear();
|
||||
@ -3568,76 +3568,6 @@ namespace ts {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index where arrayA and arrayB differ
|
||||
*/
|
||||
export function findDiffIndex<T>(arrayA: ReadonlyArray<T>, arrayB: ReadonlyArray<T>) {
|
||||
for (let i = 0; i < arrayA.length; i++) {
|
||||
if (i === arrayB.length || arrayA[i] !== arrayB[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return arrayA.length;
|
||||
}
|
||||
|
||||
export function addFileWatcher(host: System, file: string, cb: FileWatcherCallback): FileWatcher {
|
||||
return host.watchFile(file, cb);
|
||||
}
|
||||
|
||||
export function addFileWatcherWithLogging(host: System, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher {
|
||||
const watcherCaption = `FileWatcher:: `;
|
||||
return createWatcherWithLogging(addFileWatcher, watcherCaption, log, host, file, cb);
|
||||
}
|
||||
|
||||
export type FilePathWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void;
|
||||
export function addFilePathWatcher(host: System, file: string, cb: FilePathWatcherCallback, path: Path): FileWatcher {
|
||||
return host.watchFile(file, (fileName, eventKind) => cb(fileName, eventKind, path));
|
||||
}
|
||||
|
||||
export function addFilePathWatcherWithLogging(host: System, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher {
|
||||
const watcherCaption = `FileWatcher:: `;
|
||||
return createWatcherWithLogging(addFileWatcher, watcherCaption, log, host, file, cb, path);
|
||||
}
|
||||
|
||||
export function addDirectoryWatcher(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher {
|
||||
const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0;
|
||||
return host.watchDirectory(directory, cb, recursive);
|
||||
}
|
||||
|
||||
export function addDirectoryWatcherWithLogging(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher {
|
||||
const watcherCaption = `DirectoryWatcher ${(flags & WatchDirectoryFlags.Recursive) !== 0 ? "recursive" : ""}:: `;
|
||||
return createWatcherWithLogging(addDirectoryWatcher, watcherCaption, log, host, directory, cb, flags);
|
||||
}
|
||||
|
||||
type WatchCallback<T, U> = (fileName: string, cbOptional1?: T, optional?: U) => void;
|
||||
type AddWatch<T, U> = (host: System, file: string, cb: WatchCallback<T, U>, optional?: U) => FileWatcher;
|
||||
function createWatcherWithLogging<T, U>(addWatch: AddWatch<T, U>, watcherCaption: string, log: (s: string) => void, host: System, file: string, cb: WatchCallback<T, U>, optional?: U): FileWatcher {
|
||||
const info = `PathInfo: ${file}`;
|
||||
log(`${watcherCaption}Added: ${info}`);
|
||||
const watcher = addWatch(host, file, (fileName, cbOptional1?) => {
|
||||
const optionalInfo = cbOptional1 !== undefined ? ` ${cbOptional1}` : "";
|
||||
log(`${watcherCaption}Trigger: ${fileName}${optionalInfo} ${info}`);
|
||||
const start = timestamp();
|
||||
cb(fileName, cbOptional1, optional);
|
||||
const elapsed = timestamp() - start;
|
||||
log(`${watcherCaption}Elapsed: ${elapsed}ms Trigger: ${fileName}${optionalInfo} ${info}`);
|
||||
}, optional);
|
||||
return {
|
||||
close: () => {
|
||||
log(`${watcherCaption}Close: ${info}`);
|
||||
watcher.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function closeFileWatcher(watcher: FileWatcher) {
|
||||
watcher.close();
|
||||
}
|
||||
|
||||
export function closeFileWatcherOf<T extends { watcher: FileWatcher; }>(objWithWatcher: T) {
|
||||
objWithWatcher.watcher.close();
|
||||
}
|
||||
|
||||
/** Calls `callback` on `directory` and every ancestor directory it has, returning the first defined result. */
|
||||
export function forEachAncestorDirectory<T>(directory: string, callback: (directory: string) => T): T {
|
||||
while (true) {
|
||||
|
||||
@ -245,7 +245,7 @@ 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 hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations
|
||||
let changedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed
|
||||
let hasChangedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed
|
||||
|
||||
const loggingEnabled = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics;
|
||||
const writeLog: (s: string) => void = loggingEnabled ? s => system.write(s) : noop;
|
||||
@ -269,26 +269,25 @@ namespace ts {
|
||||
|
||||
const compilerHost: CompilerHost & ResolutionCacheHost = {
|
||||
// Members for CompilerHost
|
||||
getSourceFile: getVersionedSourceFile,
|
||||
getSourceFile: (fileName, languageVersion, onError?, shouldCreateNewSourceFile?) => getVersionedSourceFileByPath(fileName, toPath(fileName), languageVersion, onError, shouldCreateNewSourceFile),
|
||||
getSourceFileByPath: getVersionedSourceFileByPath,
|
||||
getDefaultLibLocation,
|
||||
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
|
||||
writeFile: (_fileName, _data, _writeByteOrderMark, _onError?, _sourceFiles?) => { },
|
||||
writeFile: notImplemented,
|
||||
getCurrentDirectory,
|
||||
useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames,
|
||||
getCanonicalFileName,
|
||||
getNewLine: () => newLine,
|
||||
fileExists,
|
||||
readFile,
|
||||
trace,
|
||||
directoryExists,
|
||||
readFile: fileName => system.readFile(fileName),
|
||||
trace: s => system.write(s + newLine),
|
||||
directoryExists: directoryName => directoryStructureHost.directoryExists(directoryName),
|
||||
getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "",
|
||||
getDirectories,
|
||||
getDirectories: path => directoryStructureHost.getDirectories(path),
|
||||
realpath,
|
||||
resolveTypeReferenceDirectives,
|
||||
resolveModuleNames,
|
||||
resolveTypeReferenceDirectives: (typeDirectiveNames, containingFile) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile),
|
||||
resolveModuleNames: (moduleNames, containingFile, reusedNames?) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, /*logChanges*/ false),
|
||||
onReleaseOldSourceFile,
|
||||
hasChangedAutomaticTypeDirectiveNames,
|
||||
// Members for ResolutionCacheHost
|
||||
toPath,
|
||||
getCompilationSettings: () => compilerOptions,
|
||||
@ -296,12 +295,14 @@ namespace ts {
|
||||
watchTypeRootsDirectory: watchDirectory,
|
||||
getCachedDirectoryStructureHost,
|
||||
onInvalidatedResolution: scheduleProgramUpdate,
|
||||
onChangedAutomaticTypeDirectiveNames,
|
||||
onChangedAutomaticTypeDirectiveNames: () => {
|
||||
hasChangedAutomaticTypeDirectiveNames = true;
|
||||
scheduleProgramUpdate();
|
||||
},
|
||||
writeLog
|
||||
};
|
||||
// Cache for the module resolution
|
||||
const resolutionCache = createResolutionCache(compilerHost);
|
||||
resolutionCache.setRootDirectory(configFileName ?
|
||||
const resolutionCache = createResolutionCache(compilerHost, configFileName ?
|
||||
getDirectoryPath(getNormalizedAbsolutePath(configFileName, getCurrentDirectory())) :
|
||||
getCurrentDirectory()
|
||||
);
|
||||
@ -337,6 +338,7 @@ namespace ts {
|
||||
// Compile the program
|
||||
resolutionCache.startCachingPerDirectoryResolution();
|
||||
compilerHost.hasInvalidatedResolution = hasInvalidatedResolution;
|
||||
compilerHost.hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames;
|
||||
program = createProgram(rootFileNames, compilerOptions, compilerHost, program);
|
||||
resolutionCache.finishCachingPerDirectoryResolution();
|
||||
builder.onProgramUpdateGraph(program, hasInvalidatedResolution);
|
||||
@ -379,38 +381,10 @@ namespace ts {
|
||||
return directoryStructureHost.fileExists(fileName);
|
||||
}
|
||||
|
||||
function directoryExists(directoryName: string) {
|
||||
return directoryStructureHost.directoryExists(directoryName);
|
||||
}
|
||||
|
||||
function readFile(fileName: string) {
|
||||
return system.readFile(fileName);
|
||||
}
|
||||
|
||||
function trace(s: string) {
|
||||
return system.write(s + newLine);
|
||||
}
|
||||
|
||||
function getDirectories(path: string) {
|
||||
return directoryStructureHost.getDirectories(path);
|
||||
}
|
||||
|
||||
function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[]) {
|
||||
return resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, /*logChanges*/ false);
|
||||
}
|
||||
|
||||
function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string) {
|
||||
return resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile);
|
||||
}
|
||||
|
||||
function getDefaultLibLocation(): string {
|
||||
return getDirectoryPath(normalizePath(system.getExecutingFilePath()));
|
||||
}
|
||||
|
||||
function getVersionedSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile {
|
||||
return getVersionedSourceFileByPath(fileName, toPath(fileName), languageVersion, onError, shouldCreateNewSourceFile);
|
||||
}
|
||||
|
||||
function getVersionedSourceFileByPath(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile {
|
||||
const hostSourceFile = sourceFilesCache.get(path);
|
||||
// No source file on the host
|
||||
@ -594,15 +568,6 @@ namespace ts {
|
||||
return watchDirectoryWorker(system, directory, cb, flags, writeLog);
|
||||
}
|
||||
|
||||
function onChangedAutomaticTypeDirectiveNames() {
|
||||
changedAutomaticTypeDirectiveNames = true;
|
||||
scheduleProgramUpdate();
|
||||
}
|
||||
|
||||
function hasChangedAutomaticTypeDirectiveNames() {
|
||||
return changedAutomaticTypeDirectiveNames;
|
||||
}
|
||||
|
||||
function watchMissingFilePath(missingFilePath: Path) {
|
||||
return watchFilePath(system, missingFilePath, onMissingFileChange, missingFilePath, writeLog);
|
||||
}
|
||||
@ -633,19 +598,19 @@ namespace ts {
|
||||
function watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags) {
|
||||
return watchDirectory(
|
||||
directory,
|
||||
fileOrFolder => {
|
||||
fileOrDirectory => {
|
||||
Debug.assert(!!configFileName);
|
||||
|
||||
const fileOrFolderPath = toPath(fileOrFolder);
|
||||
const fileOrDirectoryPath = toPath(fileOrDirectory);
|
||||
|
||||
// Since the file existance changed, update the sourceFiles cache
|
||||
(directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath);
|
||||
removeSourceFile(fileOrFolderPath);
|
||||
(directoryStructureHost as CachedDirectoryStructureHost).addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
|
||||
removeSourceFile(fileOrDirectoryPath);
|
||||
|
||||
// If the the added or created file or folder is not supported file name, ignore the file
|
||||
// If the the added or created file or directory 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}`);
|
||||
if (fileOrDirectoryPath !== directory && !isSupportedSourceFileName(fileOrDirectory, compilerOptions)) {
|
||||
writeLog(`Project: ${configFileName} Detected file add/remove of non supported extension: ${fileOrDirectory}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
136
src/compiler/watchUtilities.ts
Normal file
136
src/compiler/watchUtilities.ts
Normal file
@ -0,0 +1,136 @@
|
||||
/// <reference path="core.ts" />
|
||||
|
||||
namespace ts {
|
||||
/**
|
||||
* Updates the existing missing file watches with the new set of missing files after new program is created
|
||||
*/
|
||||
export function updateMissingFilePathsWatch(
|
||||
program: Program,
|
||||
missingFileWatches: Map<FileWatcher>,
|
||||
createMissingFileWatch: (missingFilePath: Path) => FileWatcher,
|
||||
) {
|
||||
const missingFilePaths = program.getMissingFilePaths();
|
||||
const newMissingFilePathMap = arrayToSet(missingFilePaths);
|
||||
// Update the missing file paths watcher
|
||||
mutateMap(
|
||||
missingFileWatches,
|
||||
newMissingFilePathMap,
|
||||
{
|
||||
// Watch the missing files
|
||||
createNewValue: createMissingFileWatch,
|
||||
// Files that are no longer missing (e.g. because they are no longer required)
|
||||
// should no longer be watched.
|
||||
onDeleteValue: closeFileWatcher
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export interface WildcardDirectoryWatcher {
|
||||
watcher: FileWatcher;
|
||||
flags: WatchDirectoryFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the existing wild card directory watches with the new set of wild card directories from the config file
|
||||
* after new program is created because the config file was reloaded or program was created first time from the config file
|
||||
* Note that there is no need to call this function when the program is updated with additional files without reloading config files,
|
||||
* as wildcard directories wont change unless reloading config file
|
||||
*/
|
||||
export function updateWatchingWildcardDirectories(
|
||||
existingWatchedForWildcards: Map<WildcardDirectoryWatcher>,
|
||||
wildcardDirectories: Map<WatchDirectoryFlags>,
|
||||
watchDirectory: (directory: string, flags: WatchDirectoryFlags) => FileWatcher
|
||||
) {
|
||||
mutateMap(
|
||||
existingWatchedForWildcards,
|
||||
wildcardDirectories,
|
||||
{
|
||||
// Create new watch and recursive info
|
||||
createNewValue: createWildcardDirectoryWatcher,
|
||||
// Close existing watch thats not needed any more
|
||||
onDeleteValue: closeFileWatcherOf,
|
||||
// Close existing watch that doesnt match in the flags
|
||||
onExistingValue: updateWildcardDirectoryWatcher
|
||||
}
|
||||
);
|
||||
|
||||
function createWildcardDirectoryWatcher(directory: string, flags: WatchDirectoryFlags): WildcardDirectoryWatcher {
|
||||
// Create new watch and recursive info
|
||||
return {
|
||||
watcher: watchDirectory(directory, flags),
|
||||
flags
|
||||
};
|
||||
}
|
||||
|
||||
function updateWildcardDirectoryWatcher(existingWatcher: WildcardDirectoryWatcher, flags: WatchDirectoryFlags, directory: string) {
|
||||
// Watcher needs to be updated if the recursive flags dont match
|
||||
if (existingWatcher.flags === flags) {
|
||||
return;
|
||||
}
|
||||
|
||||
existingWatcher.watcher.close();
|
||||
existingWatchedForWildcards.set(directory, createWildcardDirectoryWatcher(directory, flags));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
export function addFileWatcher(host: System, file: string, cb: FileWatcherCallback): FileWatcher {
|
||||
return host.watchFile(file, cb);
|
||||
}
|
||||
|
||||
export function addFileWatcherWithLogging(host: System, file: string, cb: FileWatcherCallback, log: (s: string) => void): FileWatcher {
|
||||
const watcherCaption = `FileWatcher:: `;
|
||||
return createWatcherWithLogging(addFileWatcher, watcherCaption, log, host, file, cb);
|
||||
}
|
||||
|
||||
export type FilePathWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void;
|
||||
export function addFilePathWatcher(host: System, file: string, cb: FilePathWatcherCallback, path: Path): FileWatcher {
|
||||
return host.watchFile(file, (fileName, eventKind) => cb(fileName, eventKind, path));
|
||||
}
|
||||
|
||||
export function addFilePathWatcherWithLogging(host: System, file: string, cb: FilePathWatcherCallback, path: Path, log: (s: string) => void): FileWatcher {
|
||||
const watcherCaption = `FileWatcher:: `;
|
||||
return createWatcherWithLogging(addFileWatcher, watcherCaption, log, host, file, cb, path);
|
||||
}
|
||||
|
||||
export function addDirectoryWatcher(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher {
|
||||
const recursive = (flags & WatchDirectoryFlags.Recursive) !== 0;
|
||||
return host.watchDirectory(directory, cb, recursive);
|
||||
}
|
||||
|
||||
export function addDirectoryWatcherWithLogging(host: System, directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags, log: (s: string) => void): FileWatcher {
|
||||
const watcherCaption = `DirectoryWatcher ${(flags & WatchDirectoryFlags.Recursive) !== 0 ? "recursive" : ""}:: `;
|
||||
return createWatcherWithLogging(addDirectoryWatcher, watcherCaption, log, host, directory, cb, flags);
|
||||
}
|
||||
|
||||
type WatchCallback<T, U> = (fileName: string, cbOptional1?: T, optional?: U) => void;
|
||||
type AddWatch<T, U> = (host: System, file: string, cb: WatchCallback<T, U>, optional?: U) => FileWatcher;
|
||||
function createWatcherWithLogging<T, U>(addWatch: AddWatch<T, U>, watcherCaption: string, log: (s: string) => void, host: System, file: string, cb: WatchCallback<T, U>, optional?: U): FileWatcher {
|
||||
const info = `PathInfo: ${file}`;
|
||||
log(`${watcherCaption}Added: ${info}`);
|
||||
const watcher = addWatch(host, file, (fileName, cbOptional1?) => {
|
||||
const optionalInfo = cbOptional1 !== undefined ? ` ${cbOptional1}` : "";
|
||||
log(`${watcherCaption}Trigger: ${fileName}${optionalInfo} ${info}`);
|
||||
const start = timestamp();
|
||||
cb(fileName, cbOptional1, optional);
|
||||
const elapsed = timestamp() - start;
|
||||
log(`${watcherCaption}Elapsed: ${elapsed}ms Trigger: ${fileName}${optionalInfo} ${info}`);
|
||||
}, optional);
|
||||
return {
|
||||
close: () => {
|
||||
log(`${watcherCaption}Close: ${info}`);
|
||||
watcher.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function closeFileWatcher(watcher: FileWatcher) {
|
||||
watcher.close();
|
||||
}
|
||||
|
||||
export function closeFileWatcherOf<T extends { watcher: FileWatcher; }>(objWithWatcher: T) {
|
||||
objWithWatcher.watcher.close();
|
||||
}
|
||||
}
|
||||
@ -885,7 +885,7 @@ namespace ts {
|
||||
program, newRootFileNames, newOptions,
|
||||
path => program.getSourceFileByPath(path).version, /*fileExists*/ returnFalse,
|
||||
/*hasInvalidatedResolution*/ returnFalse,
|
||||
/*hasChangedAutomaticTypeDirectiveNames*/ returnFalse
|
||||
/*hasChangedAutomaticTypeDirectiveNames*/ false
|
||||
);
|
||||
assert.isTrue(actual);
|
||||
}
|
||||
|
||||
@ -967,7 +967,7 @@ namespace ts.projectSystem {
|
||||
checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, libFile.path]);
|
||||
});
|
||||
|
||||
it("should resuse same project if file is opened from the configured project that has no open files", () => {
|
||||
it("should reuse same project if file is opened from the configured project that has no open files", () => {
|
||||
const file1 = {
|
||||
path: "/a/b/main.ts",
|
||||
content: "let x =1;"
|
||||
@ -4639,7 +4639,7 @@ namespace ts.projectSystem {
|
||||
verifyProjectAndWatchedDirectories();
|
||||
callsTrackingHost.verifyNoHostCalls();
|
||||
|
||||
function getFilePathIfOpen(f: FileOrFolder) {
|
||||
function getFilePathIfNotOpen(f: FileOrFolder) {
|
||||
const path = toCanonical(f.path);
|
||||
const info = projectService.getScriptInfoForPath(toCanonical(f.path));
|
||||
return info && info.isScriptOpen() ? undefined : path;
|
||||
@ -4647,7 +4647,7 @@ namespace ts.projectSystem {
|
||||
|
||||
function verifyProjectAndWatchedDirectories() {
|
||||
checkProjectActualFiles(project, map(projectFiles, f => f.path));
|
||||
checkWatchedFiles(host, mapDefined(projectFiles, getFilePathIfOpen));
|
||||
checkWatchedFiles(host, mapDefined(projectFiles, getFilePathIfNotOpen));
|
||||
checkWatchedDirectories(host, watchingRecursiveDirectories, /*recursive*/ true);
|
||||
checkWatchedDirectories(host, [], /*recursive*/ false);
|
||||
}
|
||||
|
||||
@ -246,17 +246,17 @@ namespace ts.TestFSWithWatch {
|
||||
const mapNewLeaves = createMap<true>();
|
||||
const isNewFs = this.fs.size === 0;
|
||||
// always inject safelist file in the list of files
|
||||
for (const fileOrFolder of fileOrFolderList.concat(this.withSafeList ? safeList : [])) {
|
||||
const path = this.toFullPath(fileOrFolder.path);
|
||||
for (const fileOrDirectory of fileOrFolderList.concat(this.withSafeList ? safeList : [])) {
|
||||
const path = this.toFullPath(fileOrDirectory.path);
|
||||
mapNewLeaves.set(path, true);
|
||||
// If its a change
|
||||
const currentEntry = this.fs.get(path);
|
||||
if (currentEntry) {
|
||||
if (isFile(currentEntry)) {
|
||||
if (isString(fileOrFolder.content)) {
|
||||
if (isString(fileOrDirectory.content)) {
|
||||
// Update file
|
||||
if (currentEntry.content !== fileOrFolder.content) {
|
||||
currentEntry.content = fileOrFolder.content;
|
||||
if (currentEntry.content !== fileOrDirectory.content) {
|
||||
currentEntry.content = fileOrDirectory.content;
|
||||
this.invokeFileWatcher(currentEntry.fullPath, FileWatcherEventKind.Changed);
|
||||
}
|
||||
}
|
||||
@ -266,7 +266,7 @@ namespace ts.TestFSWithWatch {
|
||||
}
|
||||
else {
|
||||
// Folder
|
||||
if (isString(fileOrFolder.content)) {
|
||||
if (isString(fileOrDirectory.content)) {
|
||||
// TODO: Changing from folder => file
|
||||
}
|
||||
else {
|
||||
@ -275,32 +275,32 @@ namespace ts.TestFSWithWatch {
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.ensureFileOrFolder(fileOrFolder);
|
||||
this.ensureFileOrFolder(fileOrDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isNewFs) {
|
||||
this.fs.forEach((fileOrFolder, path) => {
|
||||
this.fs.forEach((fileOrDirectory, path) => {
|
||||
// If this entry is not from the new file or folder
|
||||
if (!mapNewLeaves.get(path)) {
|
||||
// Leaf entries that arent in new list => remove these
|
||||
if (isFile(fileOrFolder) || isFolder(fileOrFolder) && fileOrFolder.entries.length === 0) {
|
||||
this.removeFileOrFolder(fileOrFolder, folder => !mapNewLeaves.get(folder.path));
|
||||
if (isFile(fileOrDirectory) || isFolder(fileOrDirectory) && fileOrDirectory.entries.length === 0) {
|
||||
this.removeFileOrFolder(fileOrDirectory, folder => !mapNewLeaves.get(folder.path));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ensureFileOrFolder(fileOrFolder: FileOrFolder) {
|
||||
if (isString(fileOrFolder.content)) {
|
||||
const file = this.toFile(fileOrFolder);
|
||||
ensureFileOrFolder(fileOrDirectory: FileOrFolder) {
|
||||
if (isString(fileOrDirectory.content)) {
|
||||
const file = this.toFile(fileOrDirectory);
|
||||
Debug.assert(!this.fs.get(file.path));
|
||||
const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath));
|
||||
this.addFileOrFolderInFolder(baseFolder, file);
|
||||
}
|
||||
else {
|
||||
const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory);
|
||||
const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory);
|
||||
this.ensureFolder(fullPath);
|
||||
}
|
||||
}
|
||||
@ -326,44 +326,44 @@ namespace ts.TestFSWithWatch {
|
||||
return folder;
|
||||
}
|
||||
|
||||
private addFileOrFolderInFolder(folder: Folder, fileOrFolder: File | Folder) {
|
||||
folder.entries.push(fileOrFolder);
|
||||
this.fs.set(fileOrFolder.path, fileOrFolder);
|
||||
private addFileOrFolderInFolder(folder: Folder, fileOrDirectory: File | Folder) {
|
||||
folder.entries.push(fileOrDirectory);
|
||||
this.fs.set(fileOrDirectory.path, fileOrDirectory);
|
||||
|
||||
if (isFile(fileOrFolder)) {
|
||||
this.invokeFileWatcher(fileOrFolder.fullPath, FileWatcherEventKind.Created);
|
||||
if (isFile(fileOrDirectory)) {
|
||||
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Created);
|
||||
}
|
||||
this.invokeDirectoryWatcher(folder.fullPath, fileOrFolder.fullPath);
|
||||
this.invokeDirectoryWatcher(folder.fullPath, fileOrDirectory.fullPath);
|
||||
}
|
||||
|
||||
private removeFileOrFolder(fileOrFolder: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean) {
|
||||
const basePath = getDirectoryPath(fileOrFolder.path);
|
||||
private removeFileOrFolder(fileOrDirectory: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean) {
|
||||
const basePath = getDirectoryPath(fileOrDirectory.path);
|
||||
const baseFolder = this.fs.get(basePath) as Folder;
|
||||
if (basePath !== fileOrFolder.path) {
|
||||
if (basePath !== fileOrDirectory.path) {
|
||||
Debug.assert(!!baseFolder);
|
||||
filterMutate(baseFolder.entries, entry => entry !== fileOrFolder);
|
||||
filterMutate(baseFolder.entries, entry => entry !== fileOrDirectory);
|
||||
}
|
||||
this.fs.delete(fileOrFolder.path);
|
||||
this.fs.delete(fileOrDirectory.path);
|
||||
|
||||
if (isFile(fileOrFolder)) {
|
||||
this.invokeFileWatcher(fileOrFolder.fullPath, FileWatcherEventKind.Deleted);
|
||||
if (isFile(fileOrDirectory)) {
|
||||
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted);
|
||||
}
|
||||
else {
|
||||
Debug.assert(fileOrFolder.entries.length === 0);
|
||||
const relativePath = this.getRelativePathToDirectory(fileOrFolder.fullPath, fileOrFolder.fullPath);
|
||||
Debug.assert(fileOrDirectory.entries.length === 0);
|
||||
const relativePath = this.getRelativePathToDirectory(fileOrDirectory.fullPath, fileOrDirectory.fullPath);
|
||||
// Invoke directory and recursive directory watcher for the folder
|
||||
// Here we arent invoking recursive directory watchers for the base folders
|
||||
// since that is something we would want to do for both file as well as folder we are deleting
|
||||
invokeWatcherCallbacks(this.watchedDirectories.get(fileOrFolder.path), cb => this.directoryCallback(cb, relativePath));
|
||||
invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(fileOrFolder.path), cb => this.directoryCallback(cb, relativePath));
|
||||
invokeWatcherCallbacks(this.watchedDirectories.get(fileOrDirectory.path), cb => this.directoryCallback(cb, relativePath));
|
||||
invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(fileOrDirectory.path), cb => this.directoryCallback(cb, relativePath));
|
||||
}
|
||||
|
||||
if (basePath !== fileOrFolder.path) {
|
||||
if (basePath !== fileOrDirectory.path) {
|
||||
if (baseFolder.entries.length === 0 && isRemovableLeafFolder(baseFolder)) {
|
||||
this.removeFileOrFolder(baseFolder, isRemovableLeafFolder);
|
||||
}
|
||||
else {
|
||||
this.invokeRecursiveDirectoryWatcher(baseFolder.fullPath, fileOrFolder.fullPath);
|
||||
this.invokeRecursiveDirectoryWatcher(baseFolder.fullPath, fileOrDirectory.fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -402,13 +402,13 @@ namespace ts.TestFSWithWatch {
|
||||
}
|
||||
}
|
||||
|
||||
private toFile(fileOrFolder: FileOrFolder): File {
|
||||
const fullPath = getNormalizedAbsolutePath(fileOrFolder.path, this.currentDirectory);
|
||||
private toFile(fileOrDirectory: FileOrFolder): File {
|
||||
const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory);
|
||||
return {
|
||||
path: this.toPath(fullPath),
|
||||
content: fileOrFolder.content,
|
||||
content: fileOrDirectory.content,
|
||||
fullPath,
|
||||
fileSize: fileOrFolder.fileSize
|
||||
fileSize: fileOrDirectory.fileSize
|
||||
};
|
||||
}
|
||||
|
||||
@ -477,7 +477,7 @@ namespace ts.TestFSWithWatch {
|
||||
});
|
||||
}
|
||||
|
||||
watchDirectory(directoryName: string, cb: DirectoryWatcherCallback, recursive: boolean): DirectoryWatcher {
|
||||
watchDirectory(directoryName: string, cb: DirectoryWatcherCallback, recursive: boolean): FileWatcher {
|
||||
const path = this.toFullPath(directoryName);
|
||||
const map = recursive ? this.watchedDirectoriesRecursive : this.watchedDirectories;
|
||||
const callback: TestDirectoryWatcher = {
|
||||
@ -486,8 +486,6 @@ namespace ts.TestFSWithWatch {
|
||||
};
|
||||
map.add(path, callback);
|
||||
return {
|
||||
referenceCount: 0,
|
||||
directoryName,
|
||||
close: () => map.remove(path, callback)
|
||||
};
|
||||
}
|
||||
|
||||
@ -349,7 +349,7 @@ namespace ts.server {
|
||||
private readonly projectToSizeMap: Map<number> = createMap<number>();
|
||||
/**
|
||||
* This is a map of config file paths existance that doesnt need query to disk
|
||||
* - The entry can be present because there is inferred project that needs to watch addition of config file to folder
|
||||
* - The entry can be present because there is inferred project that needs to watch addition of config file to directory
|
||||
* In this case the exists could be true/false based on config file is present or not
|
||||
* - Or it is present if we have configured project open with config file at that location
|
||||
* In this case the exists property is always true
|
||||
@ -753,15 +753,15 @@ namespace ts.server {
|
||||
return this.watchDirectory(
|
||||
this.host,
|
||||
directory,
|
||||
fileOrFolder => {
|
||||
const fileOrFolderPath = this.toPath(fileOrFolder);
|
||||
project.getCachedDirectoryStructureHost().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath);
|
||||
fileOrDirectory => {
|
||||
const fileOrDirectoryPath = this.toPath(fileOrDirectory);
|
||||
project.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
|
||||
const configFilename = project.getConfigFilePath();
|
||||
|
||||
// If the the added or created file or folder is not supported file name, ignore the file
|
||||
// If the the added or created file or directory 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, project.getCompilationSettings(), this.hostConfiguration.extraFileExtensions)) {
|
||||
this.logger.info(`Project: ${configFilename} Detected file add/remove of non supported extension: ${fileOrFolder}`);
|
||||
if (fileOrDirectoryPath !== directory && !isSupportedSourceFileName(fileOrDirectory, project.getCompilationSettings(), this.hostConfiguration.extraFileExtensions)) {
|
||||
this.logger.info(`Project: ${configFilename} Detected file add/remove of non supported extension: ${fileOrDirectory}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -833,7 +833,7 @@ namespace ts.server {
|
||||
switch (project.projectKind) {
|
||||
case ProjectKind.External:
|
||||
unorderedRemoveItem(this.externalProjects, <ExternalProject>project);
|
||||
this.projectToSizeMap.delete((project as ExternalProject).externalProjectName);
|
||||
this.projectToSizeMap.delete(project.getProjectName());
|
||||
break;
|
||||
case ProjectKind.Configured:
|
||||
this.configuredProjects.delete((<ConfiguredProject>project).canonicalConfigFilePath);
|
||||
@ -852,7 +852,7 @@ namespace ts.server {
|
||||
|
||||
const project = this.getOrCreateInferredProjectForProjectRootPathIfEnabled(info, projectRootPath) ||
|
||||
this.getOrCreateSingleInferredProjectIfEnabled() ||
|
||||
this.createInferredProject();
|
||||
this.createInferredProject(getDirectoryPath(info.path));
|
||||
|
||||
project.addRoot(info);
|
||||
project.updateGraph();
|
||||
@ -1374,7 +1374,7 @@ namespace ts.server {
|
||||
|
||||
this.addFilesToNonInferredProjectAndUpdateGraph(project, files, externalFilePropertyReader, typeAcquisition);
|
||||
this.externalProjects.push(project);
|
||||
this.sendProjectTelemetry(project.externalProjectName, project);
|
||||
this.sendProjectTelemetry(projectFileName, project);
|
||||
return project;
|
||||
}
|
||||
|
||||
@ -1461,7 +1461,7 @@ namespace ts.server {
|
||||
this.addFilesToNonInferredProjectAndUpdateGraph(project, filesToAdd, fileNamePropertyReader, projectOptions.typeAcquisition);
|
||||
this.configuredProjects.set(project.canonicalConfigFilePath, project);
|
||||
this.setConfigFileExistenceByNewConfiguredProject(project);
|
||||
this.sendProjectTelemetry(project.getConfigFilePath(), project, projectOptions);
|
||||
this.sendProjectTelemetry(configFileName, project, projectOptions);
|
||||
return project;
|
||||
}
|
||||
|
||||
@ -1584,7 +1584,7 @@ namespace ts.server {
|
||||
return project;
|
||||
}
|
||||
}
|
||||
return this.createInferredProject(/*isSingleInferredProject*/ false, projectRootPath);
|
||||
return this.createInferredProject(projectRootPath, /*isSingleInferredProject*/ false, projectRootPath);
|
||||
}
|
||||
|
||||
// we don't have an explicit root path, so we should try to find an inferred project
|
||||
@ -1621,12 +1621,12 @@ namespace ts.server {
|
||||
return this.inferredProjects[0];
|
||||
}
|
||||
|
||||
return this.createInferredProject(/*isSingleInferredProject*/ true);
|
||||
return this.createInferredProject(/*rootDirectoryForResolution*/ undefined, /*isSingleInferredProject*/ true);
|
||||
}
|
||||
|
||||
private createInferredProject(isSingleInferredProject?: boolean, projectRootPath?: string): InferredProject {
|
||||
private createInferredProject(rootDirectoryForResolution: string | undefined, isSingleInferredProject?: boolean, projectRootPath?: string): InferredProject {
|
||||
const compilerOptions = projectRootPath && this.compilerOptionsForInferredProjectsPerProjectRoot.get(projectRootPath) || this.compilerOptionsForInferredProjects;
|
||||
const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath);
|
||||
const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath, rootDirectoryForResolution);
|
||||
if (isSingleInferredProject) {
|
||||
this.inferredProjects.unshift(project);
|
||||
}
|
||||
@ -1669,10 +1669,12 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
getOrCreateScriptInfoNotOpenedByClientForNormalizedPath(fileName: NormalizedPath, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) {
|
||||
return this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ false, /*fileContent*/ undefined, scriptKind, hasMixedContent, hostToQueryFileExistsOn);
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, hostToQueryFileExistsOn?: DirectoryStructureHost) {
|
||||
return this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent, hostToQueryFileExistsOn);
|
||||
}
|
||||
|
||||
@ -162,7 +162,8 @@ namespace ts.server {
|
||||
*/
|
||||
private projectStateVersion = 0;
|
||||
|
||||
private changedAutomaticTypeDirectiveNames = false;
|
||||
/*@internal*/
|
||||
hasChangedAutomaticTypeDirectiveNames = false;
|
||||
|
||||
private typingFiles: SortedReadonlyArray<string>;
|
||||
|
||||
@ -201,7 +202,8 @@ namespace ts.server {
|
||||
languageServiceEnabled: boolean,
|
||||
private compilerOptions: CompilerOptions,
|
||||
public compileOnSaveEnabled: boolean,
|
||||
/*@internal*/public directoryStructureHost: DirectoryStructureHost) {
|
||||
/*@internal*/public directoryStructureHost: DirectoryStructureHost,
|
||||
rootDirectoryForResolution: string | undefined) {
|
||||
|
||||
if (!this.compilerOptions) {
|
||||
this.compilerOptions = getDefaultCompilerOptions();
|
||||
@ -224,7 +226,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
this.languageService = createLanguageService(this, this.documentRegistry);
|
||||
this.resolutionCache = createResolutionCache(this);
|
||||
this.resolutionCache = createResolutionCache(this, rootDirectoryForResolution);
|
||||
if (!languageServiceEnabled) {
|
||||
this.disableLanguageService();
|
||||
}
|
||||
@ -244,29 +246,26 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getScriptFileNames() {
|
||||
const result: string[] = [];
|
||||
if (this.rootFiles) {
|
||||
this.rootFilesMap.forEach((value, _path) => {
|
||||
const f: ScriptInfo = isScriptInfo(value) && value;
|
||||
if (this.languageServiceEnabled || (f && f.isScriptOpen())) {
|
||||
// if language service is disabled - process only files that are open
|
||||
result.push(f ? f.fileName : value as NormalizedPath);
|
||||
}
|
||||
});
|
||||
if (this.typingFiles) {
|
||||
for (const f of this.typingFiles) {
|
||||
result.push(f);
|
||||
}
|
||||
}
|
||||
if (!this.rootFiles) {
|
||||
return ts.emptyArray;
|
||||
}
|
||||
return result;
|
||||
|
||||
let result: string[] | undefined;
|
||||
this.rootFilesMap.forEach(value => {
|
||||
if (this.languageServiceEnabled || (isScriptInfo(value) && value.isScriptOpen())) {
|
||||
// if language service is disabled - process only files that are open
|
||||
(result || (result = [])).push(isScriptInfo(value) ? value.fileName : value);
|
||||
}
|
||||
});
|
||||
|
||||
return addRange(result, this.typingFiles) || ts.emptyArray;
|
||||
}
|
||||
|
||||
private getScriptInfoLSHost(fileName: string) {
|
||||
private getOrCreateScriptInfoAndAttachToProject(fileName: string) {
|
||||
const scriptInfo = this.projectService.getOrCreateScriptInfoNotOpenedByClient(fileName, this.directoryStructureHost);
|
||||
if (scriptInfo) {
|
||||
const existingValue = this.rootFilesMap.get(scriptInfo.path);
|
||||
if (existingValue !== undefined && existingValue !== scriptInfo) {
|
||||
if (existingValue !== scriptInfo && existingValue !== undefined) {
|
||||
// This was missing path earlier but now the file exists. Update the root
|
||||
this.rootFiles.push(scriptInfo);
|
||||
this.rootFilesMap.set(scriptInfo.path, scriptInfo);
|
||||
@ -277,17 +276,17 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getScriptKind(fileName: string) {
|
||||
const info = this.getScriptInfoLSHost(fileName);
|
||||
const info = this.getOrCreateScriptInfoAndAttachToProject(fileName);
|
||||
return info && info.scriptKind;
|
||||
}
|
||||
|
||||
getScriptVersion(filename: string) {
|
||||
const info = this.getScriptInfoLSHost(filename);
|
||||
const info = this.getOrCreateScriptInfoAndAttachToProject(filename);
|
||||
return info && info.getLatestVersion();
|
||||
}
|
||||
|
||||
getScriptSnapshot(filename: string): IScriptSnapshot {
|
||||
const scriptInfo = this.getScriptInfoLSHost(filename);
|
||||
const scriptInfo = this.getOrCreateScriptInfoAndAttachToProject(filename);
|
||||
if (scriptInfo) {
|
||||
return scriptInfo.getSnapshot();
|
||||
}
|
||||
@ -377,15 +376,10 @@ namespace ts.server {
|
||||
|
||||
/*@internal*/
|
||||
onChangedAutomaticTypeDirectiveNames() {
|
||||
this.changedAutomaticTypeDirectiveNames = true;
|
||||
this.hasChangedAutomaticTypeDirectiveNames = true;
|
||||
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
hasChangedAutomaticTypeDirectiveNames() {
|
||||
return this.changedAutomaticTypeDirectiveNames;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
getGlobalCache() {
|
||||
return this.getTypeAcquisition().enable ? this.projectService.typingsInstaller.globalTypingsCacheLocation : undefined;
|
||||
@ -802,7 +796,7 @@ namespace ts.server {
|
||||
// - oldProgram is not set - this is a first time updateGraph is called
|
||||
// - newProgram is different from the old program and structure of the old program was not reused.
|
||||
const hasChanges = !oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely));
|
||||
this.changedAutomaticTypeDirectiveNames = false;
|
||||
this.hasChangedAutomaticTypeDirectiveNames = false;
|
||||
if (hasChanges) {
|
||||
if (oldProgram) {
|
||||
for (const f of oldProgram.getSourceFiles()) {
|
||||
@ -1035,7 +1029,12 @@ namespace ts.server {
|
||||
super.setCompilerOptions(newOptions);
|
||||
}
|
||||
|
||||
constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions, public readonly projectRootPath?: string | undefined) {
|
||||
constructor(
|
||||
projectService: ProjectService,
|
||||
documentRegistry: DocumentRegistry,
|
||||
compilerOptions: CompilerOptions,
|
||||
public readonly projectRootPath: string | undefined,
|
||||
rootDirectoryForResolution: string | undefined) {
|
||||
super(InferredProject.newName(),
|
||||
ProjectKind.Inferred,
|
||||
projectService,
|
||||
@ -1044,7 +1043,8 @@ namespace ts.server {
|
||||
/*languageServiceEnabled*/ true,
|
||||
compilerOptions,
|
||||
/*compileOnSaveEnabled*/ false,
|
||||
projectService.host);
|
||||
projectService.host,
|
||||
rootDirectoryForResolution);
|
||||
}
|
||||
|
||||
addRoot(info: ScriptInfo) {
|
||||
@ -1053,13 +1053,6 @@ namespace ts.server {
|
||||
this.toggleJsInferredProject(/*isJsInferredProject*/ true);
|
||||
}
|
||||
super.addRoot(info);
|
||||
// For first root set the resolution cache root dir
|
||||
if (this.getRootFiles.length === 1) {
|
||||
const rootDirPath = this.getProjectRootPath();
|
||||
if (rootDirPath) {
|
||||
this.resolutionCache.setRootDirectory(rootDirPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeRoot(info: ScriptInfo) {
|
||||
@ -1081,11 +1074,9 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getProjectRootPath() {
|
||||
// Single inferred project does not have a project root.
|
||||
if (this.projectService.useSingleInferredProject) {
|
||||
return undefined;
|
||||
}
|
||||
return this.projectRootPath || getDirectoryPath(this.getRootFiles()[0]);
|
||||
return this.projectRootPath ||
|
||||
// Single inferred project does not have a project root.
|
||||
!this.projectService.useSingleInferredProject && getDirectoryPath(this.getRootFiles()[0]);
|
||||
}
|
||||
|
||||
close() {
|
||||
@ -1135,10 +1126,18 @@ namespace ts.server {
|
||||
languageServiceEnabled: boolean,
|
||||
public compileOnSaveEnabled: boolean,
|
||||
cachedDirectoryStructureHost: CachedDirectoryStructureHost) {
|
||||
super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, cachedDirectoryStructureHost);
|
||||
super(configFileName,
|
||||
ProjectKind.Configured,
|
||||
projectService,
|
||||
documentRegistry,
|
||||
hasExplicitListOfFiles,
|
||||
languageServiceEnabled,
|
||||
compilerOptions,
|
||||
compileOnSaveEnabled,
|
||||
cachedDirectoryStructureHost,
|
||||
getDirectoryPath(configFileName));
|
||||
this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName));
|
||||
this.enablePlugins();
|
||||
this.resolutionCache.setRootDirectory(getDirectoryPath(configFileName));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1370,8 +1369,15 @@ namespace ts.server {
|
||||
languageServiceEnabled: boolean,
|
||||
public compileOnSaveEnabled: boolean,
|
||||
private readonly projectFilePath?: string) {
|
||||
super(externalProjectName, ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, projectService.host);
|
||||
this.resolutionCache.setRootDirectory(this.getProjectRootPath());
|
||||
super(externalProjectName,
|
||||
ProjectKind.External,
|
||||
projectService,
|
||||
documentRegistry,
|
||||
/*hasExplicitListOfFiles*/ true,
|
||||
languageServiceEnabled, compilerOptions,
|
||||
compileOnSaveEnabled,
|
||||
projectService.host,
|
||||
getDirectoryPath(projectFilePath || normalizeSlashes(externalProjectName)));
|
||||
}
|
||||
|
||||
getExcludedFiles() {
|
||||
|
||||
@ -4,17 +4,36 @@ namespace ts.server {
|
||||
|
||||
/* @internal */
|
||||
export class TextStorage {
|
||||
// Generated only on demand and removes the text if any edits
|
||||
/**
|
||||
* Generated only on demand (based on edits, or information requested)
|
||||
* The property text is set to undefined when edits happen on the cache
|
||||
*/
|
||||
private svc: ScriptVersionCache | undefined;
|
||||
private svcVersion = 0;
|
||||
|
||||
// Store text when there is no svc or svc has no change, on edit, remove the text
|
||||
/**
|
||||
* Stores the text when there are no changes to the script version cache
|
||||
* The script version cache is generated on demand and text is still retained.
|
||||
* Only on edits to the script version cache, the text will be set to undefined
|
||||
*/
|
||||
private text: string;
|
||||
/**
|
||||
* Line map for the text when there is no script version cache present
|
||||
*/
|
||||
private lineMap: number[];
|
||||
private textVersion = 0;
|
||||
|
||||
/**
|
||||
* True if the text is for the file thats open in the editor
|
||||
*/
|
||||
public isOpen: boolean;
|
||||
/**
|
||||
* True if the text present is the text from the file on the disk
|
||||
*/
|
||||
private ownFileText: boolean;
|
||||
/**
|
||||
* True when reloading contents of file from the disk is pending
|
||||
*/
|
||||
private pendingReloadFromDisk: boolean;
|
||||
|
||||
constructor(private readonly host: ServerHost, private readonly fileName: NormalizedPath) {
|
||||
|
||||
@ -207,7 +207,7 @@ namespace ts.JsTyping {
|
||||
}
|
||||
|
||||
// depth of 2, so we access `node_modules/foo` but not `node_modules/foo/bar`
|
||||
const fileNames = host.readDirectory(packagesFolderPath, [".json"], /*excludes*/ undefined, /*includes*/ undefined, /*depth*/ 2);
|
||||
const fileNames = host.readDirectory(packagesFolderPath, [Extension.Json], /*excludes*/ undefined, /*includes*/ undefined, /*depth*/ 2);
|
||||
if (log) log(`Searching for typing names in ${packagesFolderPath}; all files: ${JSON.stringify(fileNames)}`);
|
||||
const packageNames: string[] = [];
|
||||
for (const fileName of fileNames) {
|
||||
|
||||
@ -1101,13 +1101,11 @@ namespace ts {
|
||||
}
|
||||
|
||||
function synchronizeHostData(): void {
|
||||
const hasChangedAutomaticTypeDirectiveNames = host.hasChangedAutomaticTypeDirectiveNames && host.hasChangedAutomaticTypeDirectiveNames.bind(host) || returnFalse;
|
||||
|
||||
// perform fast check if host supports it
|
||||
if (host.getProjectVersion) {
|
||||
const hostProjectVersion = host.getProjectVersion();
|
||||
if (hostProjectVersion) {
|
||||
if (lastProjectVersion === hostProjectVersion && !hasChangedAutomaticTypeDirectiveNames()) {
|
||||
if (lastProjectVersion === hostProjectVersion && !host.hasChangedAutomaticTypeDirectiveNames) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1129,7 +1127,7 @@ namespace ts {
|
||||
const hasInvalidatedResolution: HasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse;
|
||||
|
||||
// If the program is already up-to-date, we can reuse it
|
||||
if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache.getVersion(path), fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames)) {
|
||||
if (isProgramUptoDate(program, rootFileNames, hostCache.compilationSettings(), path => hostCache.getVersion(path), fileExists, hasInvalidatedResolution, host.hasChangedAutomaticTypeDirectiveNames)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1170,7 +1168,7 @@ namespace ts {
|
||||
},
|
||||
onReleaseOldSourceFile,
|
||||
hasInvalidatedResolution,
|
||||
hasChangedAutomaticTypeDirectiveNames
|
||||
hasChangedAutomaticTypeDirectiveNames: host.hasChangedAutomaticTypeDirectiveNames
|
||||
};
|
||||
if (host.trace) {
|
||||
compilerHost.trace = message => host.trace(message);
|
||||
|
||||
@ -186,7 +186,7 @@ namespace ts {
|
||||
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[];
|
||||
resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
|
||||
hasInvalidatedResolution?: HasInvalidatedResolution;
|
||||
hasChangedAutomaticTypeDirectiveNames?(): boolean;
|
||||
hasChangedAutomaticTypeDirectiveNames?: boolean;
|
||||
directoryExists?(directoryName: string): boolean;
|
||||
|
||||
/*
|
||||
@ -278,6 +278,7 @@ namespace ts {
|
||||
getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[];
|
||||
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined;
|
||||
|
||||
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
|
||||
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean): EmitOutput | EmitOutputDetailed;
|
||||
|
||||
getProgram(): Program;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user