Skip to content

Commit 16cf7c4

Browse files
committed
Watch for the automatic types that included as part of type resolution
1 parent a3b9467 commit 16cf7c4

File tree

11 files changed

+179
-39
lines changed

11 files changed

+179
-39
lines changed

src/compiler/core.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,9 @@ namespace ts {
12241224
/** Do nothing and return false */
12251225
export function returnFalse(): false { return false; }
12261226

1227+
/** Do nothing and return true */
1228+
export function returnTrue(): true { return true; }
1229+
12271230
/** Throws an error because a function is not implemented. */
12281231
export function notImplemented(): never {
12291232
throw new Error("Not implemented");

src/compiler/program.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -393,10 +393,17 @@ namespace ts {
393393
allDiagnostics?: Diagnostic[];
394394
}
395395

396-
export function isProgramUptoDate(program: Program | undefined, rootFileNames: string[], newOptions: CompilerOptions,
397-
getSourceVersion: (path: Path) => string, fileExists: (fileName: string) => boolean, hasInvalidatedResolution: HasInvalidatedResolution): boolean {
398-
// If we haven't create a program yet, then it is not up-to-date
399-
if (!program) {
396+
export function isProgramUptoDate(
397+
program: Program | undefined,
398+
rootFileNames: string[],
399+
newOptions: CompilerOptions,
400+
getSourceVersion: (path: Path) => string,
401+
fileExists: (fileName: string) => boolean,
402+
hasInvalidatedResolution: HasInvalidatedResolution,
403+
hasChangedAutomaticTypeDirectiveNames: () => boolean,
404+
): boolean {
405+
// If we haven't create a program yet or has changed automatic type directives, then it is not up-to-date
406+
if (!program || hasChangedAutomaticTypeDirectiveNames()) {
400407
return false;
401408
}
402409

@@ -587,6 +594,7 @@ namespace ts {
587594
let moduleResolutionCache: ModuleResolutionCache;
588595
let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string) => ResolvedModuleFull[];
589596
const hasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse;
597+
const hasChangedAutomaticTypeDirectiveNames = host.hasChangedAutomaticTypeDirectiveNames && host.hasChangedAutomaticTypeDirectiveNames.bind(host) || returnFalse;
590598
if (host.resolveModuleNames) {
591599
resolveModuleNamesWorker = (moduleNames, containingFile) => host.resolveModuleNames(checkAllDefined(moduleNames), containingFile).map(resolved => {
592600
// An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName.
@@ -1090,6 +1098,10 @@ namespace ts {
10901098
return oldProgram.structureIsReused;
10911099
}
10921100

1101+
if (hasChangedAutomaticTypeDirectiveNames()) {
1102+
return oldProgram.structureIsReused = StructureIsReused.SafeModules;
1103+
}
1104+
10931105
missingFilePaths = oldProgram.getMissingFilePaths();
10941106

10951107
// update fileName -> file mapping

src/compiler/resolutionCache.ts

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ namespace ts {
1919

2020
setRootDirectory(dir: string): void;
2121

22+
updateTypeRootsWatch(): void;
23+
closeTypeRootsWatch(): void;
24+
2225
clear(): void;
2326
}
2427

@@ -39,6 +42,8 @@ namespace ts {
3942
getCompilationSettings(): CompilerOptions;
4043
watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher;
4144
onInvalidatedResolution(): void;
45+
watchTypeRootsDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher;
46+
onChangedAutomaticTypeDirectiveNames(): void;
4247
getCachedPartialSystem?(): CachedPartialSystem;
4348
projectName?: string;
4449
getGlobalCache?(): string | undefined;
@@ -59,10 +64,16 @@ namespace ts {
5964
const resolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
6065
const perDirectoryResolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
6166

67+
const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory());
68+
6269
const directoryWatchesOfFailedLookups = createMap<DirectoryWatchesOfFailedLookup>();
6370
const failedLookupLocationToDirPath = createMap<Path>();
6471
let rootDir: string;
6572
let rootPath: Path;
73+
74+
// TypeRoot watches for the types that get added as part of getAutomaticTypeDirectiveNames
75+
const typeRootsWatches = createMap<FileWatcher>();
76+
6677
return {
6778
startRecordingFilesWithChangedResolutions,
6879
finishRecordingFilesWithChangedResolutions,
@@ -73,12 +84,14 @@ namespace ts {
7384
invalidateResolutionOfFile,
7485
createHasInvalidatedResolution,
7586
setRootDirectory,
87+
updateTypeRootsWatch,
88+
closeTypeRootsWatch,
7689
clear
7790
};
7891

7992
function setRootDirectory(dir: string) {
8093
Debug.assert(!resolvedModuleNames.size && !resolvedTypeReferenceDirectives.size && !directoryWatchesOfFailedLookups.size);
81-
rootDir = removeTrailingDirectorySeparator(getNormalizedAbsolutePath(dir, resolutionHost.getCurrentDirectory()));
94+
rootDir = removeTrailingDirectorySeparator(getNormalizedAbsolutePath(dir, getCurrentDirectory()));
8295
rootPath = resolutionHost.toPath(rootDir);
8396
}
8497

@@ -93,6 +106,7 @@ namespace ts {
93106
// Close all the watches for failed lookup locations, irrespective of refcounts for them since this is to clear the cache
94107
clearMap(directoryWatchesOfFailedLookups, closeFileWatcherOf);
95108
failedLookupLocationToDirPath.clear();
109+
closeTypeRootsWatch();
96110
resolvedModuleNames.clear();
97111
resolvedTypeReferenceDirectives.clear();
98112
Debug.assert(perDirectoryResolvedModuleNames.size === 0 && perDirectoryResolvedTypeReferenceDirectives.size === 0);
@@ -283,7 +297,7 @@ namespace ts {
283297
}
284298

285299
let dirPath = getDirectoryPath(failedLookupLocationPath);
286-
let dir = getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, resolutionHost.getCurrentDirectory()));
300+
let dir = getDirectoryPath(getNormalizedAbsolutePath(failedLookupLocation, getCurrentDirectory()));
287301
for (let i = 0; i < MAX_DIRPATHS_TO_RECURSE; i++) {
288302
const parentPath = getDirectoryPath(dirPath);
289303
if (directoryWatchesOfFailedLookups.has(dirPath) || parentPath === dirPath) {
@@ -455,5 +469,55 @@ namespace ts {
455469
invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedModuleNames, isInDirPath);
456470
invalidateResolutionCacheOfChangedFailedLookupLocation(resolvedTypeReferenceDirectives, isInDirPath);
457471
}
472+
473+
function closeTypeRootsWatch() {
474+
clearMap(typeRootsWatches, closeFileWatcher);
475+
}
476+
477+
function createTypeRootsWatch(_typeRootPath: string, typeRoot: string): FileWatcher {
478+
// Create new watch and recursive info
479+
return resolutionHost.watchTypeRootsDirectory(typeRoot, fileOrFolder => {
480+
const fileOrFolderPath = resolutionHost.toPath(fileOrFolder);
481+
if (resolutionHost.getCachedPartialSystem) {
482+
// Since the file existance changed, update the sourceFiles cache
483+
resolutionHost.getCachedPartialSystem().addOrDeleteFileOrFolder(fileOrFolder, fileOrFolderPath);
484+
}
485+
486+
// For now just recompile
487+
// We could potentially store more data here about whether it was/would be really be used or not
488+
// and with that determine to trigger compilation but for now this is enough
489+
resolutionHost.onChangedAutomaticTypeDirectiveNames();
490+
}, WatchDirectoryFlags.Recursive);
491+
}
492+
493+
/**
494+
* Watches the types that would get added as part of getAutomaticTypeDirectiveNames
495+
* To be called when compiler options change
496+
*/
497+
function updateTypeRootsWatch() {
498+
const options = resolutionHost.getCompilationSettings();
499+
if (options.types) {
500+
// No need to do any watch since resolution cache is going to handle the failed lookups
501+
// for the types added by this
502+
closeTypeRootsWatch();
503+
return;
504+
}
505+
506+
// we need to assume the directories exist to ensure that we can get all the type root directories that get included
507+
const typeRoots = getEffectiveTypeRoots(options, { directoryExists: returnTrue, getCurrentDirectory });
508+
if (typeRoots) {
509+
mutateMap(
510+
typeRootsWatches,
511+
arrayToMap(typeRoots, tr => resolutionHost.toPath(tr)),
512+
{
513+
createNewValue: createTypeRootsWatch,
514+
onDeleteValue: closeFileWatcher
515+
}
516+
);
517+
}
518+
else {
519+
closeTypeRootsWatch();
520+
}
521+
}
458522
}
459523
}

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4067,6 +4067,7 @@ namespace ts {
40674067
getEnvironmentVariable?(name: string): string;
40684068
onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions): void;
40694069
hasInvalidatedResolution?: HasInvalidatedResolution;
4070+
hasChangedAutomaticTypeDirectiveNames?(): boolean;
40704071
}
40714072

40724073
/* @internal */

src/compiler/watchedProgram.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -246,12 +246,13 @@ namespace ts {
246246
const sourceFilesCache = createMap<HostFileInfo | string>(); // Cache that stores the source file and version info
247247
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
248248
let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations
249+
let changedAutomaticTypeDirectiveNames = false; // True if the automatic type directives have changed
249250

250251
const loggingEnabled = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics;
251252
const writeLog: (s: string) => void = loggingEnabled ? s => system.write(s) : noop;
252253
const watchFile = loggingEnabled ? ts.addFileWatcherWithLogging : ts.addFileWatcher;
253254
const watchFilePath = loggingEnabled ? ts.addFilePathWatcherWithLogging : ts.addFilePathWatcher;
254-
const watchDirectory = loggingEnabled ? ts.addDirectoryWatcherWithLogging : ts.addDirectoryWatcher;
255+
const watchDirectoryWorker = loggingEnabled ? ts.addDirectoryWatcherWithLogging : ts.addDirectoryWatcher;
255256

256257
watchingHost = watchingHost || createWatchingSystemHost(compilerOptions.pretty);
257258
const { system, parseConfigFile, reportDiagnostic, reportWatchDiagnostic, beforeCompile, afterCompile } = watchingHost;
@@ -288,12 +289,15 @@ namespace ts {
288289
resolveTypeReferenceDirectives,
289290
resolveModuleNames,
290291
onReleaseOldSourceFile,
292+
hasChangedAutomaticTypeDirectiveNames,
291293
// Members for ResolutionCacheHost
292294
toPath,
293295
getCompilationSettings: () => compilerOptions,
294-
watchDirectoryOfFailedLookupLocation,
296+
watchDirectoryOfFailedLookupLocation: watchDirectory,
297+
watchTypeRootsDirectory: watchDirectory,
295298
getCachedPartialSystem,
296299
onInvalidatedResolution: scheduleProgramUpdate,
300+
onChangedAutomaticTypeDirectiveNames,
297301
writeLog
298302
};
299303
// Cache for the module resolution
@@ -320,13 +324,14 @@ namespace ts {
320324
}
321325

322326
const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution();
323-
if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution)) {
327+
if (isProgramUptoDate(program, rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames)) {
324328
return;
325329
}
326330

327331
if (hasChangedCompilerOptions && changesAffectModuleResolution(program && program.getCompilerOptions(), compilerOptions)) {
328332
resolutionCache.clear();
329333
}
334+
const needsUpdateInTypeRootWatch = hasChangedCompilerOptions || !program;
330335
hasChangedCompilerOptions = false;
331336
beforeCompile(compilerOptions);
332337

@@ -339,6 +344,10 @@ namespace ts {
339344

340345
// Update watches
341346
updateMissingFilePathsWatch(program, missingFilesMap || (missingFilesMap = createMap()), watchMissingFilePath);
347+
if (needsUpdateInTypeRootWatch) {
348+
resolutionCache.updateTypeRootsWatch();
349+
}
350+
342351
if (missingFilePathsRequestedForRelease) {
343352
// These are the paths that program creater told us as not in use any more but were missing on the disk.
344353
// We didnt remove the entry for them from sourceFiles cache so that we dont have to do File IO,
@@ -582,8 +591,17 @@ namespace ts {
582591
}
583592
}
584593

585-
function watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) {
586-
return watchDirectory(system, directory, cb, flags, writeLog);
594+
function watchDirectory(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags) {
595+
return watchDirectoryWorker(system, directory, cb, flags, writeLog);
596+
}
597+
598+
function onChangedAutomaticTypeDirectiveNames() {
599+
changedAutomaticTypeDirectiveNames = true;
600+
scheduleProgramUpdate();
601+
}
602+
603+
function hasChangedAutomaticTypeDirectiveNames() {
604+
return changedAutomaticTypeDirectiveNames;
587605
}
588606

589607
function watchMissingFilePath(missingFilePath: Path) {
@@ -615,7 +633,6 @@ namespace ts {
615633

616634
function watchWildcardDirectory(directory: string, flags: WatchDirectoryFlags) {
617635
return watchDirectory(
618-
system,
619636
directory,
620637
fileOrFolder => {
621638
Debug.assert(!!configFileName);
@@ -645,8 +662,7 @@ namespace ts {
645662
scheduleProgramUpdate();
646663
}
647664
},
648-
flags,
649-
writeLog
665+
flags
650666
);
651667
}
652668

src/harness/unittests/tscWatchMode.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,8 @@ namespace ts.tscWatch {
167167
checkProgramActualFiles(watch(), [file1.path, libFile.path, file2.path]);
168168
checkProgramRootFiles(watch(), [file1.path, file2.path]);
169169
checkWatchedFiles(host, [configFile.path, file1.path, file2.path, libFile.path]);
170-
checkWatchedDirectories(host, [getDirectoryPath(configFile.path)], /*recursive*/ true);
170+
const configDir = getDirectoryPath(configFile.path);
171+
checkWatchedDirectories(host, projectSystem.getTypeRootsFromLocation(configDir).concat(configDir), /*recursive*/ true);
171172
});
172173

173174
// TODO: if watching for config file creation
@@ -181,7 +182,8 @@ namespace ts.tscWatch {
181182
};
182183
const host = createWatchedSystem([commonFile1, libFile, configFile]);
183184
const watch = createWatchModeWithConfigFile(configFile.path, host);
184-
checkWatchedDirectories(host, ["/a/b"], /*recursive*/ true);
185+
const configDir = getDirectoryPath(configFile.path);
186+
checkWatchedDirectories(host, projectSystem.getTypeRootsFromLocation(configDir).concat(configDir), /*recursive*/ true);
185187

186188
checkProgramRootFiles(watch(), [commonFile1.path]);
187189

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,22 @@ namespace ts.projectSystem {
321321
verifyDiagnostics(actual, []);
322322
}
323323

324+
const typeRootFromTsserverLocation = "/node_modules/@types";
325+
326+
export function getTypeRootsFromLocation(currentDirectory: string) {
327+
currentDirectory = normalizePath(currentDirectory);
328+
const result: string[] = [];
329+
while (true) {
330+
result.push(combinePaths(currentDirectory, "node_modules/@types"));
331+
const parentDirectory = getDirectoryPath(currentDirectory);
332+
if (parentDirectory === currentDirectory) {
333+
break;
334+
}
335+
currentDirectory = parentDirectory;
336+
}
337+
return result;
338+
}
339+
324340
describe("tsserverProjectSystem", () => {
325341
const commonFile1: FileOrFolder = {
326342
path: "/a/b/commonFile1.ts",
@@ -359,7 +375,7 @@ namespace ts.projectSystem {
359375
const configFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]);
360376
checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path));
361377
checkWatchedDirectories(host, [], /*recursive*/ false);
362-
checkWatchedDirectories(host, ["/"], /*recursive*/ true);
378+
checkWatchedDirectories(host, ["/", typeRootFromTsserverLocation], /*recursive*/ true);
363379
});
364380

365381
it("can handle tsconfig file name with difference casing", () => {
@@ -430,7 +446,8 @@ namespace ts.projectSystem {
430446
checkProjectRootFiles(project, [file1.path, file2.path]);
431447
// watching all files except one that was open
432448
checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]);
433-
checkWatchedDirectories(host, [getDirectoryPath(configFile.path)], /*recursive*/ true);
449+
const configFileDirectory = getDirectoryPath(configFile.path);
450+
checkWatchedDirectories(host, getTypeRootsFromLocation(configFileDirectory).concat(configFileDirectory), /*recursive*/ true);
434451
});
435452

436453
it("create configured project with the file list", () => {
@@ -518,7 +535,8 @@ namespace ts.projectSystem {
518535
const host = createServerHost([commonFile1, libFile, configFile]);
519536
const projectService = createProjectService(host);
520537
projectService.openClientFile(commonFile1.path);
521-
checkWatchedDirectories(host, ["/a/b"], /*recursive*/ true);
538+
const configFileDir = getDirectoryPath(configFile.path);
539+
checkWatchedDirectories(host, getTypeRootsFromLocation(configFileDir).concat(configFileDir), /*recursive*/ true);
522540
checkNumberOfConfiguredProjects(projectService, 1);
523541

524542
const project = configuredProjectAt(projectService, 0);
@@ -4329,6 +4347,7 @@ namespace ts.projectSystem {
43294347
};
43304348
const appFolder = getDirectoryPath(app.path);
43314349
const projectFiles = [app, libFile, tsconfigJson];
4350+
const typeRootDirectories = getTypeRootsFromLocation(getDirectoryPath(tsconfigJson.path));
43324351
const otherFiles = [packageJson];
43334352
const host = createServerHost(projectFiles.concat(otherFiles));
43344353
const projectService = createProjectService(host);
@@ -4402,7 +4421,8 @@ namespace ts.projectSystem {
44024421
{ "path": "/a/b/node_modules/typescript" },
44034422
{ "path": "/a/b/node_modules/.bin" }
44044423
);
4405-
verifyAfterPartialOrCompleteNpmInstall(0);
4424+
// From the type root update
4425+
verifyAfterPartialOrCompleteNpmInstall(2);
44064426

44074427
forEach(filesAndFoldersToAdd, f => {
44084428
f.path = f.path
@@ -4438,7 +4458,7 @@ namespace ts.projectSystem {
44384458

44394459
const filesWatched = filter(projectFilePaths, p => p !== app.path);
44404460
checkWatchedFiles(host, filesWatched);
4441-
checkWatchedDirectories(host, recursiveWatchedDirectories, /*recursive*/ true);
4461+
checkWatchedDirectories(host, typeRootDirectories.concat(recursiveWatchedDirectories), /*recursive*/ true);
44424462
checkWatchedDirectories(host, [], /*recursive*/ false);
44434463
}
44444464
}

0 commit comments

Comments
 (0)