Skip to content

Commit 2226cd6

Browse files
authored
Merge pull request microsoft#24471 from Microsoft/watchInSiblingOfRoot
Instead of creating filter for subDirectories to watch in the ancestor directory of root, watch those subDirectories for failed lookup locations
2 parents 593b048 + 939e3e4 commit 2226cd6

File tree

4 files changed

+103
-79
lines changed

4 files changed

+103
-79
lines changed

src/compiler/resolutionCache.ts

Lines changed: 14 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,12 @@ namespace ts {
6060
watcher: FileWatcher;
6161
/** ref count keeping this directory watch alive */
6262
refCount: number;
63-
/** map of refcount for the subDirectory */
64-
subDirectoryMap?: Map<number>;
6563
}
6664

6765
interface DirectoryOfFailedLookupWatch {
6866
dir: string;
6967
dirPath: Path;
7068
ignore?: true;
71-
subDirectory?: Path;
7269
}
7370

7471
export const maxNumberOfFilesToIterateForInvalidation = 256;
@@ -403,20 +400,21 @@ namespace ts {
403400
}
404401

405402
// Use some ancestor of the root directory
406-
let subDirectory: Path | undefined;
403+
let subDirectoryPath: Path | undefined, subDirectory: string | undefined;
407404
if (rootPath !== undefined) {
408405
while (!isInDirectoryPath(dirPath, rootPath)) {
409406
const parentPath = getDirectoryPath(dirPath);
410407
if (parentPath === dirPath) {
411408
break;
412409
}
413-
subDirectory = dirPath.slice(parentPath.length + directorySeparator.length) as Path;
410+
subDirectoryPath = dirPath;
411+
subDirectory = dir;
414412
dirPath = parentPath;
415413
dir = getDirectoryPath(dir);
416414
}
417415
}
418416

419-
return filterFSRootDirectoriesToWatch({ dir, dirPath, subDirectory }, dirPath);
417+
return filterFSRootDirectoriesToWatch({ dir: subDirectory || dir, dirPath: subDirectoryPath || dirPath }, dirPath);
420418
}
421419

422420
function isPathWithDefaultFailedLookupExtension(path: Path) {
@@ -439,7 +437,7 @@ namespace ts {
439437
let setAtRoot = false;
440438
for (const failedLookupLocation of failedLookupLocations) {
441439
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
442-
const { dir, dirPath, ignore , subDirectory } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
440+
const { dir, dirPath, ignore } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
443441
if (!ignore) {
444442
// If the failed lookup location path is not one of the supported extensions,
445443
// store it in the custom path
@@ -451,7 +449,7 @@ namespace ts {
451449
setAtRoot = true;
452450
}
453451
else {
454-
setDirectoryWatcher(dir, dirPath, subDirectory);
452+
setDirectoryWatcher(dir, dirPath);
455453
}
456454
}
457455
}
@@ -461,20 +459,13 @@ namespace ts {
461459
}
462460
}
463461

464-
function setDirectoryWatcher(dir: string, dirPath: Path, subDirectory?: Path) {
465-
let dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
462+
function setDirectoryWatcher(dir: string, dirPath: Path) {
463+
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
466464
if (dirWatcher) {
467465
dirWatcher.refCount++;
468466
}
469467
else {
470-
dirWatcher = { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 };
471-
directoryWatchesOfFailedLookups.set(dirPath, dirWatcher);
472-
}
473-
474-
if (subDirectory) {
475-
const subDirectoryMap = dirWatcher.subDirectoryMap || (dirWatcher.subDirectoryMap = createMap());
476-
const existing = subDirectoryMap.get(subDirectory) || 0;
477-
subDirectoryMap.set(subDirectory, existing + 1);
468+
directoryWatchesOfFailedLookups.set(dirPath, { watcher: createDirectoryWatcher(dir, dirPath), refCount: 1 });
478469
}
479470
}
480471

@@ -492,7 +483,7 @@ namespace ts {
492483
let removeAtRoot = false;
493484
for (const failedLookupLocation of failedLookupLocations) {
494485
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
495-
const { dirPath, ignore, subDirectory } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
486+
const { dirPath, ignore } = getDirectoryToWatchFailedLookupLocation(failedLookupLocation, failedLookupLocationPath);
496487
if (!ignore) {
497488
const refCount = customFailedLookupPaths.get(failedLookupLocationPath);
498489
if (refCount) {
@@ -509,7 +500,7 @@ namespace ts {
509500
removeAtRoot = true;
510501
}
511502
else {
512-
removeDirectoryWatcher(dirPath, subDirectory);
503+
removeDirectoryWatcher(dirPath);
513504
}
514505
}
515506
}
@@ -518,30 +509,12 @@ namespace ts {
518509
}
519510
}
520511

521-
function removeDirectoryWatcher(dirPath: string, subDirectory?: Path) {
512+
function removeDirectoryWatcher(dirPath: string) {
522513
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath)!;
523-
if (subDirectory) {
524-
const existing = dirWatcher.subDirectoryMap!.get(subDirectory)!;
525-
if (existing === 1) {
526-
dirWatcher.subDirectoryMap!.delete(subDirectory);
527-
}
528-
else {
529-
dirWatcher.subDirectoryMap!.set(subDirectory, existing - 1);
530-
}
531-
}
532514
// Do not close the watcher yet since it might be needed by other failed lookup locations.
533515
dirWatcher.refCount--;
534516
}
535517

536-
function inWatchedSubdirectory(dirPath: Path, fileOrDirectoryPath: Path) {
537-
const dirWatcher = directoryWatchesOfFailedLookups.get(dirPath);
538-
if (!dirWatcher || !dirWatcher.subDirectoryMap) return false;
539-
return forEachKey(dirWatcher.subDirectoryMap, subDirectory => {
540-
const fullSubDirectory = `${dirPath}/${subDirectory}` as Path;
541-
return fullSubDirectory === fileOrDirectoryPath || isInDirectoryPath(fullSubDirectory, fileOrDirectoryPath);
542-
});
543-
}
544-
545518
function createDirectoryWatcher(directory: string, dirPath: Path) {
546519
return resolutionHost.watchDirectoryOfFailedLookupLocation(directory, fileOrDirectory => {
547520
const fileOrDirectoryPath = resolutionHost.toPath(fileOrDirectory);
@@ -550,13 +523,8 @@ namespace ts {
550523
cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
551524
}
552525

553-
// If the files are added to project root or node_modules directory, always run through the invalidation process
554-
// Otherwise run through invalidation only if adding to the immediate directory
555-
if (!allFilesHaveInvalidatedResolution &&
556-
(dirPath === rootPath || isNodeModulesDirectory(dirPath) || getDirectoryPath(fileOrDirectoryPath) === dirPath || inWatchedSubdirectory(dirPath, fileOrDirectoryPath))) {
557-
if (invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath)) {
558-
resolutionHost.onInvalidatedResolution();
559-
}
526+
if (!allFilesHaveInvalidatedResolution && invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath, dirPath === fileOrDirectoryPath)) {
527+
resolutionHost.onInvalidatedResolution();
560528
}
561529
}, WatchDirectoryFlags.Recursive);
562530
}

src/harness/unittests/tscWatchMode.ts

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ namespace ts.tscWatch {
1010
import checkArray = TestFSWithWatch.checkArray;
1111
import libFile = TestFSWithWatch.libFile;
1212
import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles;
13+
import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed;
1314
import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories;
15+
import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed;
1416
import checkOutputContains = TestFSWithWatch.checkOutputContains;
1517
import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain;
18+
import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory;
1619

1720
export function checkProgramActualFiles(program: Program, expectedFiles: string[]) {
1821
checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles);
@@ -2379,7 +2382,7 @@ declare module "fs" {
23792382
});
23802383

23812384
describe("tsc-watch when watchDirectories implementation", () => {
2382-
function verifyRenamingFileInSubFolder(tscWatchDirectory: TestFSWithWatch.Tsc_WatchDirectory) {
2385+
function verifyRenamingFileInSubFolder(tscWatchDirectory: Tsc_WatchDirectory) {
23832386
const projectFolder = "/a/username/project";
23842387
const projectSrcFolder = `${projectFolder}/src`;
23852388
const configFile: File = {
@@ -2399,8 +2402,8 @@ declare module "fs" {
23992402
const projectFolders = [projectFolder, projectSrcFolder, `${projectFolder}/node_modules/@types`];
24002403
// Watching files config file, file, lib file
24012404
const expectedWatchedFiles = files.map(f => f.path);
2402-
const expectedWatchedDirectories = tscWatchDirectory === TestFSWithWatch.Tsc_WatchDirectory.NonRecursiveWatchDirectory ? projectFolders : emptyArray;
2403-
if (tscWatchDirectory === TestFSWithWatch.Tsc_WatchDirectory.WatchFile) {
2405+
const expectedWatchedDirectories = tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory ? projectFolders : emptyArray;
2406+
if (tscWatchDirectory === Tsc_WatchDirectory.WatchFile) {
24042407
expectedWatchedFiles.push(...projectFolders);
24052408
}
24062409

@@ -2410,7 +2413,7 @@ declare module "fs" {
24102413
file.path = file.path.replace("file1.ts", "file2.ts");
24112414
expectedWatchedFiles[0] = file.path;
24122415
host.reloadFS(files);
2413-
if (tscWatchDirectory === TestFSWithWatch.Tsc_WatchDirectory.DynamicPolling) {
2416+
if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) {
24142417
// With dynamic polling the fs change would be detected only by running timeouts
24152418
host.runQueuedTimeoutCallbacks();
24162419
}
@@ -2429,21 +2432,21 @@ declare module "fs" {
24292432
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
24302433

24312434
// Watching config file, file, lib file and directories
2432-
TestFSWithWatch.checkMultiMapEachKeyWithCount("watchedFiles", host.watchedFiles, expectedWatchedFiles, 1);
2433-
TestFSWithWatch.checkMultiMapEachKeyWithCount("watchedDirectories", host.watchedDirectories, expectedWatchedDirectories, 1);
2435+
checkWatchedFilesDetailed(host, expectedWatchedFiles, 1);
2436+
checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, 1, /*recursive*/ false);
24342437
}
24352438
}
24362439

24372440
it("uses watchFile when renaming file in subfolder", () => {
2438-
verifyRenamingFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.WatchFile);
2441+
verifyRenamingFileInSubFolder(Tsc_WatchDirectory.WatchFile);
24392442
});
24402443

24412444
it("uses non recursive watchDirectory when renaming file in subfolder", () => {
2442-
verifyRenamingFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.NonRecursiveWatchDirectory);
2445+
verifyRenamingFileInSubFolder(Tsc_WatchDirectory.NonRecursiveWatchDirectory);
24432446
});
24442447

24452448
it("uses non recursive dynamic polling when renaming file in subfolder", () => {
2446-
verifyRenamingFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.DynamicPolling);
2449+
verifyRenamingFileInSubFolder(Tsc_WatchDirectory.DynamicPolling);
24472450
});
24482451

24492452
it("when there are symlinks to folders in recursive folders", () => {
@@ -2482,7 +2485,7 @@ declare module "fs" {
24822485
};
24832486
const files = [file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB];
24842487
const environmentVariables = createMap<string>();
2485-
environmentVariables.set("TSC_WATCHDIRECTORY", TestFSWithWatch.Tsc_WatchDirectory.NonRecursiveWatchDirectory);
2488+
environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory);
24862489
const host = createWatchedSystem(files, { environmentVariables, currentDirectory: cwd });
24872490
createWatchOfConfigFile("tsconfig.json", host);
24882491
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
@@ -2491,4 +2494,46 @@ declare module "fs" {
24912494
});
24922495
});
24932496
});
2497+
2498+
describe("tsc-watch with modules linked to sibling folder", () => {
2499+
const projectRoot = "/user/username/projects/project";
2500+
const mainPackageRoot = `${projectRoot}/main`;
2501+
const linkedPackageRoot = `${projectRoot}/linked-package`;
2502+
const mainFile: File = {
2503+
path: `${mainPackageRoot}/index.ts`,
2504+
content: "import { Foo } from '@scoped/linked-package'"
2505+
};
2506+
const config: File = {
2507+
path: `${mainPackageRoot}/tsconfig.json`,
2508+
content: JSON.stringify({
2509+
compilerOptions: { module: "commonjs", moduleResolution: "node", baseUrl: ".", rootDir: "." },
2510+
files: ["index.ts"]
2511+
})
2512+
};
2513+
const linkedPackageInMain: SymLink = {
2514+
path: `${mainPackageRoot}/node_modules/@scoped/linked-package`,
2515+
symLink: `${linkedPackageRoot}`
2516+
};
2517+
const linkedPackageJson: File = {
2518+
path: `${linkedPackageRoot}/package.json`,
2519+
content: JSON.stringify({ name: "@scoped/linked-package", version: "0.0.1", types: "dist/index.d.ts", main: "dist/index.js" })
2520+
};
2521+
const linkedPackageIndex: File = {
2522+
path: `${linkedPackageRoot}/dist/index.d.ts`,
2523+
content: "export * from './other';"
2524+
};
2525+
const linkedPackageOther: File = {
2526+
path: `${linkedPackageRoot}/dist/other.d.ts`,
2527+
content: 'export declare const Foo = "BAR";'
2528+
};
2529+
2530+
it("verify watched directories", () => {
2531+
const files = [libFile, mainFile, config, linkedPackageInMain, linkedPackageJson, linkedPackageIndex, linkedPackageOther];
2532+
const host = createWatchedSystem(files, { currentDirectory: mainPackageRoot });
2533+
createWatchOfConfigFile("tsconfig.json", host);
2534+
checkWatchedFilesDetailed(host, [libFile.path, mainFile.path, config.path, linkedPackageIndex.path, linkedPackageOther.path], 1);
2535+
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
2536+
checkWatchedDirectoriesDetailed(host, [mainPackageRoot, linkedPackageRoot, `${mainPackageRoot}/node_modules/@types`, `${projectRoot}/node_modules/@types`], 1, /*recursive*/ true);
2537+
});
2538+
});
24942539
}

0 commit comments

Comments
 (0)