Skip to content

Commit 560ac16

Browse files
committed
Prefer non recursive watch outside rootDir and node_modules if host prefers it
1 parent 7f9d9e5 commit 560ac16

24 files changed

+20658
-20624
lines changed

src/compiler/resolutionCache.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ export interface ResolutionCacheHost extends MinimalResolutionCacheHost {
192192
toPath(fileName: string): Path;
193193
getCanonicalFileName: GetCanonicalFileName;
194194
getCompilationSettings(): CompilerOptions;
195+
preferNonRecursiveWatch: boolean | undefined;
195196
watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher;
196197
watchAffectingFileLocation(file: string, cb: FileWatcherCallback): FileWatcher;
197198
onInvalidatedResolution(): void;
@@ -351,6 +352,7 @@ export function getDirectoryToWatchFailedLookupLocation(
351352
rootPath: Path,
352353
rootPathComponents: Readonly<PathPathComponents>,
353354
getCurrentDirectory: () => string | undefined,
355+
preferNonRecursiveWatch: boolean | undefined,
354356
): DirectoryOfFailedLookupWatch | undefined {
355357
const failedLookupPathComponents: Readonly<PathPathComponents> = getPathComponents(failedLookupLocationPath);
356358
// Ensure failed look up is normalized path
@@ -390,6 +392,7 @@ export function getDirectoryToWatchFailedLookupLocation(
390392
nodeModulesIndex,
391393
rootPathComponents,
392394
lastNodeModulesIndex,
395+
preferNonRecursiveWatch,
393396
);
394397
}
395398

@@ -401,6 +404,7 @@ function getDirectoryToWatchFromFailedLookupLocationDirectory(
401404
nodeModulesIndex: number,
402405
rootPathComponents: Readonly<PathPathComponents>,
403406
lastNodeModulesIndex: number,
407+
preferNonRecursiveWatch: boolean | undefined,
404408
): DirectoryOfFailedLookupWatch | undefined {
405409
// If directory path contains node module, get the most parent node_modules directory for watching
406410
if (nodeModulesIndex !== -1) {
@@ -412,14 +416,17 @@ function getDirectoryToWatchFromFailedLookupLocationDirectory(
412416
lastNodeModulesIndex,
413417
);
414418
}
419+
415420
// Use some ancestor of the root directory
416421
let nonRecursive = true;
417422
let length = dirPathComponentsLength;
418-
for (let i = 0; i < dirPathComponentsLength; i++) {
419-
if (dirPathComponents[i] !== rootPathComponents[i]) {
420-
nonRecursive = false;
421-
length = Math.max(i + 1, perceivedOsRootLength + 1);
422-
break;
423+
if (!preferNonRecursiveWatch) {
424+
for (let i = 0; i < dirPathComponentsLength; i++) {
425+
if (dirPathComponents[i] !== rootPathComponents[i]) {
426+
nonRecursive = false;
427+
length = Math.max(i + 1, perceivedOsRootLength + 1);
428+
break;
429+
}
423430
}
424431
}
425432
return getDirectoryOfFailedLookupWatch(
@@ -463,6 +470,7 @@ export function getDirectoryToWatchFailedLookupLocationFromTypeRoot(
463470
rootPath: Path,
464471
rootPathComponents: Readonly<PathPathComponents>,
465472
getCurrentDirectory: () => string | undefined,
473+
preferNonRecursiveWatch: boolean | undefined,
466474
filterCustomPath: (path: Path) => boolean, // Return true if this path can be used
467475
): Path | undefined {
468476
const typeRootPathComponents = getPathComponents(typeRootPath);
@@ -479,6 +487,7 @@ export function getDirectoryToWatchFailedLookupLocationFromTypeRoot(
479487
typeRootPathComponents.indexOf("node_modules" as Path),
480488
rootPathComponents,
481489
typeRootPathComponents.lastIndexOf("node_modules" as Path),
490+
preferNonRecursiveWatch,
482491
);
483492
return toWatch && filterCustomPath(toWatch.dirPath) ? toWatch.dirPath : undefined;
484493
}
@@ -1140,6 +1149,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
11401149
rootPath,
11411150
rootPathComponents,
11421151
getCurrentDirectory,
1152+
resolutionHost.preferNonRecursiveWatch,
11431153
);
11441154
if (toWatch) {
11451155
const { dir, dirPath, nonRecursive, packageDir, packageDirPath } = toWatch;
@@ -1354,6 +1364,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
13541364
rootPath,
13551365
rootPathComponents,
13561366
getCurrentDirectory,
1367+
resolutionHost.preferNonRecursiveWatch,
13571368
);
13581369
if (toWatch) {
13591370
const { dirPath, packageDirPath } = toWatch;
@@ -1662,6 +1673,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
16621673
rootPath,
16631674
rootPathComponents,
16641675
getCurrentDirectory,
1676+
resolutionHost.preferNonRecursiveWatch,
16651677
dirPath => directoryWatchesOfFailedLookups.has(dirPath) || dirPathToSymlinkPackageRefCount.has(dirPath),
16661678
);
16671679
if (dirPath) {

src/compiler/sys.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1409,6 +1409,7 @@ export interface System {
14091409
*/
14101410
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher;
14111411
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher;
1412+
/**@internal */ preferNonRecursiveWatch?: boolean;
14121413
resolvePath(path: string): string;
14131414
fileExists(path: string): boolean;
14141415
directoryExists(path: string): boolean;
@@ -1534,6 +1535,7 @@ export let sys: System = (() => {
15341535
writeFile,
15351536
watchFile,
15361537
watchDirectory,
1538+
preferNonRecursiveWatch: !fsSupportsRecursiveFsWatch,
15371539
resolvePath: path => _path.resolve(path),
15381540
fileExists,
15391541
directoryExists,

src/compiler/watch.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,7 @@ export function createWatchHost(system = sys, reportWatchStatus?: WatchStatusRep
666666
watchDirectory: maybeBind(system, system.watchDirectory) || returnNoopFileWatcher,
667667
setTimeout: maybeBind(system, system.setTimeout) || noop,
668668
clearTimeout: maybeBind(system, system.clearTimeout) || noop,
669+
preferNonRecursiveWatch: system.preferNonRecursiveWatch,
669670
};
670671
}
671672

src/compiler/watchPublic.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ export interface WatchHost {
169169
setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
170170
/** If provided, will be used to reset existing delayed compilation */
171171
clearTimeout?(timeoutId: any): void;
172+
preferNonRecursiveWatch?: boolean;
172173
}
173174
export interface ProgramHost<T extends BuilderProgram> {
174175
/**
@@ -498,6 +499,7 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
498499
compilerHost.toPath = toPath;
499500
compilerHost.getCompilationSettings = () => compilerOptions!;
500501
compilerHost.useSourceOfProjectReferenceRedirect = maybeBind(host, host.useSourceOfProjectReferenceRedirect);
502+
compilerHost.preferNonRecursiveWatch = host.preferNonRecursiveWatch;
501503
compilerHost.watchDirectoryOfFailedLookupLocation = (dir, cb, flags) => watchDirectory(dir, cb, flags, watchOptions, WatchType.FailedLookupLocations);
502504
compilerHost.watchAffectingFileLocation = (file, cb) => watchFile(file, cb, PollingInterval.High, watchOptions, WatchType.AffectingFileLocation);
503505
compilerHost.watchTypeRootsDirectory = (dir, cb, flags) => watchDirectory(dir, cb, flags, watchOptions, WatchType.TypeRoots);

src/harness/incrementalUtils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ function verifyProgram(service: ts.server.ProjectService, project: ts.server.Pro
516516
fileIsOpen: project.fileIsOpen.bind(project),
517517
getCurrentProgram: () => project.getCurrentProgram(),
518518

519+
preferNonRecursiveWatch: project.preferNonRecursiveWatch,
519520
watchDirectoryOfFailedLookupLocation: ts.returnNoopFileWatcher,
520521
watchAffectingFileLocation: ts.returnNoopFileWatcher,
521522
onInvalidatedResolution: ts.noop,

src/server/editorServices.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,13 +1075,17 @@ function getHostWatcherMap<T>(): HostWatcherMap<T> {
10751075
return { idToCallbacks: new Map(), pathToId: new Map() };
10761076
}
10771077

1078+
function getCanUseWatchEvents(service: ProjectService, canUseWatchEvents: boolean | undefined) {
1079+
return !!canUseWatchEvents && !!service.eventHandler && !!service.session;
1080+
}
1081+
10781082
function createWatchFactoryHostUsingWatchEvents(service: ProjectService, canUseWatchEvents: boolean | undefined): WatchFactoryHost | undefined {
1079-
if (!canUseWatchEvents || !service.eventHandler || !service.session) return undefined;
1083+
if (!getCanUseWatchEvents(service, canUseWatchEvents)) return undefined;
10801084
const watchedFiles = getHostWatcherMap<FileWatcherCallback>();
10811085
const watchedDirectories = getHostWatcherMap<DirectoryWatcherCallback>();
10821086
const watchedDirectoriesRecursive = getHostWatcherMap<DirectoryWatcherCallback>();
10831087
let ids = 1;
1084-
service.session.addProtocolHandler(protocol.CommandTypes.WatchChange, req => {
1088+
service.session!.addProtocolHandler(protocol.CommandTypes.WatchChange, req => {
10851089
onWatchChange((req as protocol.WatchChangeRequest).arguments);
10861090
return { responseRequired: false };
10871091
});
@@ -1326,6 +1330,7 @@ export class ProjectService {
13261330
/** @internal */ verifyDocumentRegistry = noop;
13271331
/** @internal */ verifyProgram: (project: Project) => void = noop;
13281332
/** @internal */ onProjectCreation: (project: Project) => void = noop;
1333+
/** @internal */ canUseWatchEvents: boolean;
13291334

13301335
readonly jsDocParsingMode: JSDocParsingMode | undefined;
13311336

@@ -1397,6 +1402,7 @@ export class ProjectService {
13971402
log,
13981403
getDetailWatchInfo,
13991404
);
1405+
this.canUseWatchEvents = getCanUseWatchEvents(this, opts.canUseWatchEvents);
14001406
opts.incrementalVerifier?.(this);
14011407
}
14021408

src/server/project.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
506506
protected typeAcquisition: TypeAcquisition | undefined;
507507
/** @internal */
508508
createHash = maybeBind(this.projectService.host, this.projectService.host.createHash);
509+
/** @internal*/ preferNonRecursiveWatch: boolean | undefined;
509510

510511
readonly jsDocParsingMode: JSDocParsingMode | undefined;
511512

@@ -566,6 +567,7 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
566567
this.trace = s => host.trace!(s);
567568
}
568569
this.realpath = maybeBind(host, host.realpath);
570+
this.preferNonRecursiveWatch = this.projectService.canUseWatchEvents || host.preferNonRecursiveWatch;
569571

570572
// Use the current directory as resolution root only if the project created using current directory string
571573
this.resolutionCache = createResolutionCache(

src/server/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type RequireResult = ModuleImportResult;
2020
export interface ServerHost extends System {
2121
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher;
2222
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher;
23+
preferNonRecursiveWatch?: boolean;
2324
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
2425
clearTimeout(timeoutId: any): void;
2526
setImmediate(callback: (...args: any[]) => void, ...args: any[]): any;

src/testRunner/unittests/canWatch.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ describe("unittests:: canWatch::", () => {
8787
root,
8888
rootPathCompoments,
8989
ts.returnUndefined,
90+
preferNonRecursiveWatch,
9091
);
9192
pushRow(baseline, [testPath, result ? result.packageDir ?? result.dir : "", result ? `${!result.nonRecursive}` : "", result?.packageDir ? result.dir : ""], maxLengths);
9293
});
@@ -116,6 +117,7 @@ describe("unittests:: canWatch::", () => {
116117
root,
117118
rootPathCompoments,
118119
ts.returnUndefined,
120+
preferNonRecursiveWatch,
119121
ts.returnTrue,
120122
);
121123
pushRow(baseline, [path, result !== undefined ? result : ""], maxLengths);

src/testRunner/unittests/helpers/tscWatch.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ function verifyProgramStructureAndResolutionCache(
310310
getCompilationSettings: () => options,
311311
fileIsOpen: ts.returnFalse,
312312
getCurrentProgram: () => program,
313+
preferNonRecursiveWatch: sys.preferNonRecursiveWatch,
313314

314315
watchDirectoryOfFailedLookupLocation: ts.returnNoopFileWatcher,
315316
watchAffectingFileLocation: ts.returnNoopFileWatcher,

0 commit comments

Comments
 (0)