Skip to content

Commit 209e92f

Browse files
authored
Merge pull request #22986 from Microsoft/childWatches2.8
[release-2.8] Do not watch child directories of the sym link folders
2 parents 72776b0 + 9b69093 commit 209e92f

File tree

4 files changed

+100
-37
lines changed

4 files changed

+100
-37
lines changed

src/compiler/factory.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ namespace ts {
2424
* Make `elements` into a `NodeArray<T>`. If `elements` is `undefined`, returns an empty `NodeArray<T>`.
2525
*/
2626
export function createNodeArray<T extends Node>(elements?: ReadonlyArray<T>, hasTrailingComma?: boolean): NodeArray<T> {
27-
if (elements) {
27+
if (!elements || elements === emptyArray) {
28+
elements = [];
29+
}
30+
else {
2831
if (isNodeArray(elements)) {
2932
return elements;
3033
}
3134
}
32-
else {
33-
elements = [];
34-
}
3535

3636
const array = <NodeArray<T>>elements;
3737
array.pos = -1;

src/compiler/sys.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -334,9 +334,10 @@ namespace ts {
334334
/*@internal*/
335335
export interface RecursiveDirectoryWatcherHost {
336336
watchDirectory: HostWatchDirectory;
337-
getAccessileSortedChildDirectories(path: string): ReadonlyArray<string>;
337+
getAccessibleSortedChildDirectories(path: string): ReadonlyArray<string>;
338338
directoryExists(dir: string): boolean;
339339
filePathComparer: Comparer<string>;
340+
realpath(s: string): string;
340341
}
341342

342343
/**
@@ -392,9 +393,14 @@ namespace ts {
392393
function watchChildDirectories(parentDir: string, existingChildWatches: ChildWatches, callback: DirectoryWatcherCallback): ChildWatches {
393394
let newChildWatches: DirectoryWatcher[] | undefined;
394395
enumerateInsertsAndDeletes<string, DirectoryWatcher>(
395-
host.directoryExists(parentDir) ? host.getAccessileSortedChildDirectories(parentDir) : emptyArray,
396+
host.directoryExists(parentDir) ? mapDefined(host.getAccessibleSortedChildDirectories(parentDir), child => {
397+
const childFullName = getNormalizedAbsolutePath(child, parentDir);
398+
// Filter our the symbolic link directories since those arent included in recursive watch
399+
// which is same behaviour when recursive: true is passed to fs.watch
400+
return host.filePathComparer(childFullName, host.realpath(childFullName)) === Comparison.EqualTo ? childFullName : undefined;
401+
}) : emptyArray,
396402
existingChildWatches,
397-
(child, childWatcher) => host.filePathComparer(getNormalizedAbsolutePath(child, parentDir), childWatcher.dirName),
403+
(child, childWatcher) => host.filePathComparer(child, childWatcher.dirName),
398404
createAndAddChildDirectoryWatcher,
399405
closeFileWatcher,
400406
addChildDirectoryWatcher
@@ -406,7 +412,7 @@ namespace ts {
406412
* Create new childDirectoryWatcher and add it to the new ChildDirectoryWatcher list
407413
*/
408414
function createAndAddChildDirectoryWatcher(childName: string) {
409-
const result = createDirectoryWatcher(getNormalizedAbsolutePath(childName, parentDir), callback);
415+
const result = createDirectoryWatcher(childName, callback);
410416
addChildDirectoryWatcher(result);
411417
}
412418

@@ -594,14 +600,7 @@ namespace ts {
594600
exit(exitCode?: number): void {
595601
process.exit(exitCode);
596602
},
597-
realpath(path: string): string {
598-
try {
599-
return _fs.realpathSync(path);
600-
}
601-
catch {
602-
return path;
603-
}
604-
},
603+
realpath,
605604
debugMode: some(<string[]>process.execArgv, arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)),
606605
tryEnableSourceMapsForHost() {
607606
try {
@@ -682,8 +681,9 @@ namespace ts {
682681
const watchDirectoryRecursively = createRecursiveDirectoryWatcher({
683682
filePathComparer: useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive,
684683
directoryExists,
685-
getAccessileSortedChildDirectories: path => getAccessibleFileSystemEntries(path).directories,
686-
watchDirectory
684+
getAccessibleSortedChildDirectories: path => getAccessibleFileSystemEntries(path).directories,
685+
watchDirectory,
686+
realpath
687687
});
688688

689689
return (directoryName, callback, recursive) => {
@@ -1026,6 +1026,15 @@ namespace ts {
10261026
return filter<string>(_fs.readdirSync(path), dir => fileSystemEntryExists(combinePaths(path, dir), FileSystemEntryKind.Directory));
10271027
}
10281028

1029+
function realpath(path: string): string {
1030+
try {
1031+
return _fs.realpathSync(path);
1032+
}
1033+
catch {
1034+
return path;
1035+
}
1036+
}
1037+
10291038
function getModifiedTime(path: string) {
10301039
try {
10311040
return _fs.statSync(path).mtime;

src/harness/unittests/tscWatchMode.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2355,9 +2355,53 @@ declare module "fs" {
23552355
verifyRenamingFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.NonRecursiveWatchDirectory);
23562356
});
23572357

2358-
// it("uses non recursive dynamic polling when renaming file in subfolder", () => {
2359-
// verifyRenamingFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.DynamicPolling);
2360-
// });
2358+
it("uses non recursive dynamic polling when renaming file in subfolder", () => {
2359+
verifyRenamingFileInSubFolder(TestFSWithWatch.Tsc_WatchDirectory.DynamicPolling);
2360+
});
2361+
2362+
it("when there are symlinks to folders in recursive folders", () => {
2363+
const cwd = "/home/user/projects/myproject";
2364+
const file1: FileOrFolder = {
2365+
path: `${cwd}/src/file.ts`,
2366+
content: `import * as a from "a"`
2367+
};
2368+
const tsconfig: FileOrFolder = {
2369+
path: `${cwd}/tsconfig.json`,
2370+
content: `{ "compilerOptions": { "extendedDiagnostics": true, "traceResolution": true }}`
2371+
};
2372+
const realA: FileOrFolder = {
2373+
path: `${cwd}/node_modules/reala/index.d.ts`,
2374+
content: `export {}`
2375+
};
2376+
const realB: FileOrFolder = {
2377+
path: `${cwd}/node_modules/realb/index.d.ts`,
2378+
content: `export {}`
2379+
};
2380+
const symLinkA: FileOrFolder = {
2381+
path: `${cwd}/node_modules/a`,
2382+
symLink: `${cwd}/node_modules/reala`
2383+
};
2384+
const symLinkB: FileOrFolder = {
2385+
path: `${cwd}/node_modules/b`,
2386+
symLink: `${cwd}/node_modules/realb`
2387+
};
2388+
const symLinkBInA: FileOrFolder = {
2389+
path: `${cwd}/node_modules/reala/node_modules/b`,
2390+
symLink: `${cwd}/node_modules/b`
2391+
};
2392+
const symLinkAInB: FileOrFolder = {
2393+
path: `${cwd}/node_modules/realb/node_modules/a`,
2394+
symLink: `${cwd}/node_modules/a`
2395+
};
2396+
const files = [file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB];
2397+
const environmentVariables = createMap<string>();
2398+
environmentVariables.set("TSC_WATCHDIRECTORY", TestFSWithWatch.Tsc_WatchDirectory.NonRecursiveWatchDirectory);
2399+
const host = createWatchedSystem(files, { environmentVariables, currentDirectory: cwd });
2400+
createWatchOfConfigFile("tsconfig.json", host);
2401+
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
2402+
checkWatchedDirectories(host, [cwd, `${cwd}/node_modules`, `${cwd}/node_modules/@types`, `${cwd}/node_modules/reala`, `${cwd}/node_modules/realb`,
2403+
`${cwd}/node_modules/reala/node_modules`, `${cwd}/node_modules/realb/node_modules`, `${cwd}/src`], /*recursive*/ false);
2404+
});
23612405
});
23622406
});
23632407
}

src/harness/virtualFileSystemWithWatch.ts

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ interface Array<T> {}`
8888
}
8989

9090
interface Folder extends FSEntry {
91-
entries: FSEntry[];
91+
entries: SortedArray<FSEntry>;
9292
}
9393

9494
interface SymLink extends FSEntry {
@@ -276,12 +276,14 @@ interface Array<T> {}`
276276
DynamicPolling = "RecursiveDirectoryUsingDynamicPriorityPolling"
277277
}
278278

279+
const timeIncrements = 1000;
279280
export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost, ModuleResolutionHost {
280281
args: string[] = [];
281282

282283
private readonly output: string[] = [];
283284

284285
private fs: Map<FSEntry> = createMap<FSEntry>();
286+
private time = timeIncrements;
285287
getCanonicalFileName: (s: string) => string;
286288
private toPath: (f: string) => Path;
287289
private timeoutCallbacks = new Callbacks();
@@ -310,28 +312,31 @@ interface Array<T> {}`
310312
const watchDirectory: HostWatchDirectory = (directory, cb) => this.watchFile(directory, () => cb(directory), PollingInterval.Medium);
311313
this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({
312314
directoryExists: path => this.directoryExists(path),
313-
getAccessileSortedChildDirectories: path => this.getDirectories(path),
315+
getAccessibleSortedChildDirectories: path => this.getDirectories(path),
314316
filePathComparer: this.useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive,
315-
watchDirectory
317+
watchDirectory,
318+
realpath: s => this.realpath(s)
316319
});
317320
}
318321
else if (tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory) {
319322
const watchDirectory: HostWatchDirectory = (directory, cb) => this.watchDirectory(directory, fileName => cb(fileName), /*recursive*/ false);
320323
this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({
321324
directoryExists: path => this.directoryExists(path),
322-
getAccessileSortedChildDirectories: path => this.getDirectories(path),
325+
getAccessibleSortedChildDirectories: path => this.getDirectories(path),
323326
filePathComparer: this.useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive,
324-
watchDirectory
327+
watchDirectory,
328+
realpath: s => this.realpath(s)
325329
});
326330
}
327331
else if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) {
328332
const watchFile = createDynamicPriorityPollingWatchFile(this);
329333
const watchDirectory: HostWatchDirectory = (directory, cb) => watchFile(directory, () => cb(directory), PollingInterval.Medium);
330334
this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({
331335
directoryExists: path => this.directoryExists(path),
332-
getAccessileSortedChildDirectories: path => this.getDirectories(path),
336+
getAccessibleSortedChildDirectories: path => this.getDirectories(path),
333337
filePathComparer: this.useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive,
334-
watchDirectory
338+
watchDirectory,
339+
realpath: s => this.realpath(s)
335340
});
336341
}
337342
}
@@ -355,6 +360,11 @@ interface Array<T> {}`
355360
return s;
356361
}
357362

363+
private now() {
364+
this.time += timeIncrements;
365+
return new Date(this.time);
366+
}
367+
358368
reloadFS(fileOrFolderList: ReadonlyArray<FileOrFolder>, options?: Partial<ReloadWatchInvokeOptions>) {
359369
const mapNewLeaves = createMap<true>();
360370
const isNewFs = this.fs.size === 0;
@@ -381,8 +391,8 @@ interface Array<T> {}`
381391
}
382392
else {
383393
currentEntry.content = fileOrDirectory.content;
384-
currentEntry.modifiedTime = new Date();
385-
this.fs.get(getDirectoryPath(currentEntry.path)).modifiedTime = new Date();
394+
currentEntry.modifiedTime = this.now();
395+
this.fs.get(getDirectoryPath(currentEntry.path)).modifiedTime = this.now();
386396
if (options && options.invokeDirectoryWatcherInsteadOfFileChanged) {
387397
this.invokeDirectoryWatcher(getDirectoryPath(currentEntry.fullPath), currentEntry.fullPath);
388398
}
@@ -406,7 +416,7 @@ interface Array<T> {}`
406416
}
407417
else {
408418
// Folder update: Nothing to do.
409-
currentEntry.modifiedTime = new Date();
419+
currentEntry.modifiedTime = this.now();
410420
}
411421
}
412422
}
@@ -512,8 +522,8 @@ interface Array<T> {}`
512522
}
513523

514524
private addFileOrFolderInFolder(folder: Folder, fileOrDirectory: File | Folder | SymLink, ignoreWatch?: boolean) {
515-
folder.entries.push(fileOrDirectory);
516-
folder.modifiedTime = new Date();
525+
insertSorted(folder.entries, fileOrDirectory, (a, b) => compareStringsCaseSensitive(getBaseFileName(a.path), getBaseFileName(b.path)));
526+
folder.modifiedTime = this.now();
517527
this.fs.set(fileOrDirectory.path, fileOrDirectory);
518528

519529
if (ignoreWatch) {
@@ -528,7 +538,7 @@ interface Array<T> {}`
528538
const baseFolder = this.fs.get(basePath) as Folder;
529539
if (basePath !== fileOrDirectory.path) {
530540
Debug.assert(!!baseFolder);
531-
baseFolder.modifiedTime = new Date();
541+
baseFolder.modifiedTime = this.now();
532542
filterMutate(baseFolder.entries, entry => entry !== fileOrDirectory);
533543
}
534544
this.fs.delete(fileOrDirectory.path);
@@ -603,7 +613,7 @@ interface Array<T> {}`
603613
return {
604614
path: this.toPath(fullPath),
605615
fullPath,
606-
modifiedTime: new Date()
616+
modifiedTime: this.now()
607617
};
608618
}
609619

@@ -622,7 +632,7 @@ interface Array<T> {}`
622632

623633
private toFolder(path: string): Folder {
624634
const folder = this.toFsEntry(path) as Folder;
625-
folder.entries = [];
635+
folder.entries = [] as SortedArray<FSEntry>;
626636
return folder;
627637
}
628638

@@ -642,7 +652,7 @@ interface Array<T> {}`
642652

643653
const realpath = this.realpath(path);
644654
if (path !== realpath) {
645-
return this.getRealFsEntry(isFsEntry, realpath as Path);
655+
return this.getRealFsEntry(isFsEntry, this.toPath(realpath));
646656
}
647657

648658
return undefined;

0 commit comments

Comments
 (0)