Skip to content

Commit f7833b2

Browse files
authored
On linux or editor with canUseEvents to prefer immediate directory if its not in root or node_modules (microsoft#58866)
1 parent fe0bdc8 commit f7833b2

File tree

86 files changed

+135375
-230
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+135375
-230
lines changed

src/compiler/resolutionCache.ts

+17-5
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ export interface ResolutionCacheHost extends MinimalResolutionCacheHost {
187187
toPath(fileName: string): Path;
188188
getCanonicalFileName: GetCanonicalFileName;
189189
getCompilationSettings(): CompilerOptions;
190+
preferNonRecursiveWatch: boolean | undefined;
190191
watchDirectoryOfFailedLookupLocation(directory: string, cb: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher;
191192
watchAffectingFileLocation(file: string, cb: FileWatcherCallback): FileWatcher;
192193
onInvalidatedResolution(): void;
@@ -346,6 +347,7 @@ export function getDirectoryToWatchFailedLookupLocation(
346347
rootPath: Path,
347348
rootPathComponents: Readonly<PathPathComponents>,
348349
getCurrentDirectory: () => string | undefined,
350+
preferNonRecursiveWatch: boolean | undefined,
349351
): DirectoryOfFailedLookupWatch | undefined {
350352
const failedLookupPathComponents: Readonly<PathPathComponents> = getPathComponents(failedLookupLocationPath);
351353
// Ensure failed look up is normalized path
@@ -385,6 +387,7 @@ export function getDirectoryToWatchFailedLookupLocation(
385387
nodeModulesIndex,
386388
rootPathComponents,
387389
lastNodeModulesIndex,
390+
preferNonRecursiveWatch,
388391
);
389392
}
390393

@@ -396,6 +399,7 @@ function getDirectoryToWatchFromFailedLookupLocationDirectory(
396399
nodeModulesIndex: number,
397400
rootPathComponents: Readonly<PathPathComponents>,
398401
lastNodeModulesIndex: number,
402+
preferNonRecursiveWatch: boolean | undefined,
399403
): DirectoryOfFailedLookupWatch | undefined {
400404
// If directory path contains node module, get the most parent node_modules directory for watching
401405
if (nodeModulesIndex !== -1) {
@@ -407,14 +411,17 @@ function getDirectoryToWatchFromFailedLookupLocationDirectory(
407411
lastNodeModulesIndex,
408412
);
409413
}
414+
410415
// Use some ancestor of the root directory
411416
let nonRecursive = true;
412417
let length = dirPathComponentsLength;
413-
for (let i = 0; i < dirPathComponentsLength; i++) {
414-
if (dirPathComponents[i] !== rootPathComponents[i]) {
415-
nonRecursive = false;
416-
length = Math.max(i + 1, perceivedOsRootLength + 1);
417-
break;
418+
if (!preferNonRecursiveWatch) {
419+
for (let i = 0; i < dirPathComponentsLength; i++) {
420+
if (dirPathComponents[i] !== rootPathComponents[i]) {
421+
nonRecursive = false;
422+
length = Math.max(i + 1, perceivedOsRootLength + 1);
423+
break;
424+
}
418425
}
419426
}
420427
return getDirectoryOfFailedLookupWatch(
@@ -458,6 +465,7 @@ export function getDirectoryToWatchFailedLookupLocationFromTypeRoot(
458465
rootPath: Path,
459466
rootPathComponents: Readonly<PathPathComponents>,
460467
getCurrentDirectory: () => string | undefined,
468+
preferNonRecursiveWatch: boolean | undefined,
461469
filterCustomPath: (path: Path) => boolean, // Return true if this path can be used
462470
): Path | undefined {
463471
const typeRootPathComponents = getPathComponents(typeRootPath);
@@ -474,6 +482,7 @@ export function getDirectoryToWatchFailedLookupLocationFromTypeRoot(
474482
typeRootPathComponents.indexOf("node_modules" as Path),
475483
rootPathComponents,
476484
typeRootPathComponents.lastIndexOf("node_modules" as Path),
485+
preferNonRecursiveWatch,
477486
);
478487
return toWatch && filterCustomPath(toWatch.dirPath) ? toWatch.dirPath : undefined;
479488
}
@@ -1120,6 +1129,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
11201129
rootPath,
11211130
rootPathComponents,
11221131
getCurrentDirectory,
1132+
resolutionHost.preferNonRecursiveWatch,
11231133
);
11241134
if (toWatch) {
11251135
const { dir, dirPath, nonRecursive, packageDir, packageDirPath } = toWatch;
@@ -1334,6 +1344,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
13341344
rootPath,
13351345
rootPathComponents,
13361346
getCurrentDirectory,
1347+
resolutionHost.preferNonRecursiveWatch,
13371348
);
13381349
if (toWatch) {
13391350
const { dirPath, packageDirPath } = toWatch;
@@ -1640,6 +1651,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
16401651
rootPath,
16411652
rootPathComponents,
16421653
getCurrentDirectory,
1654+
resolutionHost.preferNonRecursiveWatch,
16431655
dirPath => directoryWatchesOfFailedLookups.has(dirPath) || dirPathToSymlinkPackageRefCount.has(dirPath),
16441656
);
16451657
if (dirPath) {

src/compiler/sys.ts

+2
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

+1
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,7 @@ export function createWatchHost(system = sys, reportWatchStatus?: WatchStatusRep
667667
watchDirectory: maybeBind(system, system.watchDirectory) || returnNoopFileWatcher,
668668
setTimeout: maybeBind(system, system.setTimeout) || noop,
669669
clearTimeout: maybeBind(system, system.clearTimeout) || noop,
670+
preferNonRecursiveWatch: system.preferNonRecursiveWatch,
670671
};
671672
}
672673

src/compiler/watchPublic.ts

+2
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

+1
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,7 @@ function verifyProgram(service: ts.server.ProjectService, project: ts.server.Pro
511511
fileIsOpen: project.fileIsOpen.bind(project),
512512
getCurrentProgram: () => project.getCurrentProgram(),
513513

514+
preferNonRecursiveWatch: project.preferNonRecursiveWatch,
514515
watchDirectoryOfFailedLookupLocation: ts.returnNoopFileWatcher,
515516
watchAffectingFileLocation: ts.returnNoopFileWatcher,
516517
onInvalidatedResolution: ts.noop,

src/server/editorServices.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1083,13 +1083,17 @@ function getHostWatcherMap<T>(): HostWatcherMap<T> {
10831083
return { idToCallbacks: new Map(), pathToId: new Map() };
10841084
}
10851085

1086+
function getCanUseWatchEvents(service: ProjectService, canUseWatchEvents: boolean | undefined) {
1087+
return !!canUseWatchEvents && !!service.eventHandler && !!service.session;
1088+
}
1089+
10861090
function createWatchFactoryHostUsingWatchEvents(service: ProjectService, canUseWatchEvents: boolean | undefined): WatchFactoryHost | undefined {
1087-
if (!canUseWatchEvents || !service.eventHandler || !service.session) return undefined;
1091+
if (!getCanUseWatchEvents(service, canUseWatchEvents)) return undefined;
10881092
const watchedFiles = getHostWatcherMap<FileWatcherCallback>();
10891093
const watchedDirectories = getHostWatcherMap<DirectoryWatcherCallback>();
10901094
const watchedDirectoriesRecursive = getHostWatcherMap<DirectoryWatcherCallback>();
10911095
let ids = 1;
1092-
service.session.addProtocolHandler(protocol.CommandTypes.WatchChange, req => {
1096+
service.session!.addProtocolHandler(protocol.CommandTypes.WatchChange, req => {
10931097
onWatchChange((req as protocol.WatchChangeRequest).arguments);
10941098
return { responseRequired: false };
10951099
});
@@ -1327,6 +1331,7 @@ export class ProjectService {
13271331
/** @internal */ verifyDocumentRegistry = noop;
13281332
/** @internal */ verifyProgram: (project: Project) => void = noop;
13291333
/** @internal */ onProjectCreation: (project: Project) => void = noop;
1334+
/** @internal */ canUseWatchEvents: boolean;
13301335

13311336
readonly jsDocParsingMode: JSDocParsingMode | undefined;
13321337

@@ -1396,6 +1401,7 @@ export class ProjectService {
13961401
log,
13971402
getDetailWatchInfo,
13981403
);
1404+
this.canUseWatchEvents = getCanUseWatchEvents(this, opts.canUseWatchEvents);
13991405
opts.incrementalVerifier?.(this);
14001406
}
14011407

src/server/project.ts

+2
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,7 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
555555
protected typeAcquisition: TypeAcquisition | undefined;
556556
/** @internal */
557557
createHash = maybeBind(this.projectService.host, this.projectService.host.createHash);
558+
/** @internal*/ preferNonRecursiveWatch: boolean | undefined;
558559

559560
readonly jsDocParsingMode: JSDocParsingMode | undefined;
560561

@@ -615,6 +616,7 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
615616
this.trace = s => host.trace!(s);
616617
}
617618
this.realpath = maybeBind(host, host.realpath);
619+
this.preferNonRecursiveWatch = this.projectService.canUseWatchEvents || host.preferNonRecursiveWatch;
618620

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

src/server/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export type RequireResult = ModuleImportResult;
2929
export interface ServerHost extends System {
3030
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher;
3131
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher;
32+
preferNonRecursiveWatch?: boolean;
3233
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
3334
clearTimeout(timeoutId: any): void;
3435
setImmediate(callback: (...args: any[]) => void, ...args: any[]): any;

src/testRunner/unittests/canWatch.ts

+66-60
Original file line numberDiff line numberDiff line change
@@ -55,71 +55,77 @@ describe("unittests:: canWatch::", () => {
5555
scenario: string,
5656
forPath: "node_modules" | "node_modules/@types" | "",
5757
) {
58-
["file", "dir", "subDir"].forEach(type => {
59-
baselineCanWatch(
60-
`${scenario}In${type}`,
61-
() => `Determines whether to watch given failed lookup location (file that didnt exist) when resolving module.\r\nIt also determines the directory to watch and whether to watch it recursively or not.`,
62-
(paths, longestPathLength, baseline) => {
63-
const recursive = "Recursive";
64-
const maxLength = longestPathLength + ts.combinePaths(forPath, "dir/subdir/somefile.d.ts").length;
65-
const maxLengths = [maxLength, maxLength, recursive.length, maxLength] as const;
66-
baselineCanWatchForRoot(paths, baseline, (rootPathCompoments, root) => {
67-
pushHeader(baseline, ["Location", "getDirectoryToWatchFailedLookupLocation", recursive, "Location if not symlink"], maxLengths);
68-
paths.forEach(path => {
69-
let subPath;
70-
switch (type) {
71-
case "file":
72-
subPath = "somefile.d.ts";
73-
break;
74-
case "dir":
75-
subPath = "dir/somefile.d.ts";
76-
break;
77-
case "subDir":
78-
subPath = "dir/subdir/somefile.d.ts";
79-
break;
80-
}
81-
const testPath = combinePaths(path, forPath, subPath);
82-
const result = ts.getDirectoryToWatchFailedLookupLocation(
83-
testPath,
84-
testPath,
85-
root,
86-
root,
87-
rootPathCompoments,
88-
ts.returnUndefined,
89-
);
90-
pushRow(baseline, [testPath, result ? result.packageDir ?? result.dir : "", result ? `${!result.nonRecursive}` : "", result?.packageDir ? result.dir : ""], maxLengths);
58+
[undefined, true].forEach(preferNonRecursiveWatch => {
59+
["file", "dir", "subDir"].forEach(type => {
60+
baselineCanWatch(
61+
`${scenario}In${type}${preferNonRecursiveWatch ? "NonRecursive" : ""}`,
62+
() => `Determines whether to watch given failed lookup location (file that didnt exist) when resolving module.\r\nIt also determines the directory to watch and whether to watch it recursively or not.`,
63+
(paths, longestPathLength, baseline) => {
64+
const recursive = "Recursive";
65+
const maxLength = longestPathLength + ts.combinePaths(forPath, "dir/subdir/somefile.d.ts").length;
66+
const maxLengths = [maxLength, maxLength, recursive.length, maxLength] as const;
67+
baselineCanWatchForRoot(paths, baseline, (rootPathCompoments, root) => {
68+
pushHeader(baseline, ["Location", "getDirectoryToWatchFailedLookupLocation", recursive, "Location if not symlink"], maxLengths);
69+
paths.forEach(path => {
70+
let subPath;
71+
switch (type) {
72+
case "file":
73+
subPath = "somefile.d.ts";
74+
break;
75+
case "dir":
76+
subPath = "dir/somefile.d.ts";
77+
break;
78+
case "subDir":
79+
subPath = "dir/subdir/somefile.d.ts";
80+
break;
81+
}
82+
const testPath = combinePaths(path, forPath, subPath);
83+
const result = ts.getDirectoryToWatchFailedLookupLocation(
84+
testPath,
85+
testPath,
86+
root,
87+
root,
88+
rootPathCompoments,
89+
ts.returnUndefined,
90+
preferNonRecursiveWatch,
91+
);
92+
pushRow(baseline, [testPath, result ? result.packageDir ?? result.dir : "", result ? `${!result.nonRecursive}` : "", result?.packageDir ? result.dir : ""], maxLengths);
93+
});
9194
});
92-
});
93-
},
94-
);
95+
},
96+
);
97+
});
9598
});
9699
}
97100

98-
baselineCanWatch(
99-
"getDirectoryToWatchFailedLookupLocationFromTypeRoot",
100-
() => `When watched typeRoot handler is invoked, this method determines the directory for which the failedLookupLocation would need to be invalidated.\r\nSince this is invoked only when watching default typeRoot and is used to handle flaky directory watchers, this is used as a fail safe where if failed lookup starts with returned directory we will invalidate that resolution.`,
101-
(paths, longestPathLength, baseline) => {
102-
const maxLength = longestPathLength + "/node_modules/@types".length;
103-
const maxLengths = [maxLength, maxLength] as const;
104-
baselineCanWatchForRoot(paths, baseline, (rootPathCompoments, root) => {
105-
pushHeader(baseline, ["Directory", "getDirectoryToWatchFailedLookupLocationFromTypeRoot"], maxLengths);
106-
paths.forEach(path => {
107-
path = combinePaths(path, "node_modules/@types");
108-
// This is invoked only on paths that are watched
109-
if (!ts.canWatchAtTypes(path)) return;
110-
const result = ts.getDirectoryToWatchFailedLookupLocationFromTypeRoot(
111-
path,
112-
path,
113-
root,
114-
rootPathCompoments,
115-
ts.returnUndefined,
116-
ts.returnTrue,
117-
);
118-
pushRow(baseline, [path, result !== undefined ? result : ""], maxLengths);
101+
[undefined, true].forEach(preferNonRecursiveWatch => {
102+
baselineCanWatch(
103+
`getDirectoryToWatchFailedLookupLocationFromTypeRoot${preferNonRecursiveWatch ? "NonRecursive" : ""}`,
104+
() => `When watched typeRoot handler is invoked, this method determines the directory for which the failedLookupLocation would need to be invalidated.\r\nSince this is invoked only when watching default typeRoot and is used to handle flaky directory watchers, this is used as a fail safe where if failed lookup starts with returned directory we will invalidate that resolution.`,
105+
(paths, longestPathLength, baseline) => {
106+
const maxLength = longestPathLength + "/node_modules/@types".length;
107+
const maxLengths = [maxLength, maxLength] as const;
108+
baselineCanWatchForRoot(paths, baseline, (rootPathCompoments, root) => {
109+
pushHeader(baseline, ["Directory", "getDirectoryToWatchFailedLookupLocationFromTypeRoot"], maxLengths);
110+
paths.forEach(path => {
111+
path = combinePaths(path, "node_modules/@types");
112+
// This is invoked only on paths that are watched
113+
if (!ts.canWatchAtTypes(path)) return;
114+
const result = ts.getDirectoryToWatchFailedLookupLocationFromTypeRoot(
115+
path,
116+
path,
117+
root,
118+
rootPathCompoments,
119+
ts.returnUndefined,
120+
preferNonRecursiveWatch,
121+
ts.returnTrue,
122+
);
123+
pushRow(baseline, [path, result !== undefined ? result : ""], maxLengths);
124+
});
119125
});
120-
});
121-
},
122-
);
126+
},
127+
);
128+
});
123129

124130
function baselineCanWatchForRoot(paths: readonly ts.Path[], baseline: string[], baselineForRoot: (rootPathCompoments: Readonly<ts.PathPathComponents>, root: ts.Path) => void) {
125131
paths.forEach(rootDirForResolution => {

0 commit comments

Comments
 (0)