Make useFsEvents as default strategy for the watching (#50366)

* Remove unnecessary parameter

* Dont store name unnecessarily in the watchers

* Polled watches and not files

* Use fs events as default watching

* Some refactoring

* Make single per directory native watchers now that we are using it as default

* Rename

* Comment
This commit is contained in:
Sheetal Nandi
2022-09-06 13:14:50 -07:00
committed by GitHub
parent 5c2f770d97
commit fd05c0cc6d
404 changed files with 17019 additions and 17669 deletions
+86 -74
View File
@@ -35,8 +35,7 @@ namespace ts {
export type FileWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, modifiedTime?: Date) => void;
export type DirectoryWatcherCallback = (fileName: string) => void;
/*@internal*/
export interface WatchedFile {
interface WatchedFile {
readonly fileName: string;
readonly callback: FileWatcherCallback;
mtime: Date;
@@ -81,8 +80,7 @@ namespace ts {
/* @internal */
export let unchangedPollThresholds = createPollingIntervalBasedLevels(defaultChunkLevels);
/* @internal */
export function setCustomPollingValues(system: System) {
function setCustomPollingValues(system: System) {
if (!system.getEnvironmentVariable) {
return;
}
@@ -189,31 +187,28 @@ namespace ts {
}
}
/* @internal */
export function createDynamicPriorityPollingWatchFile(host: {
interface WatchedFileWithUnchangedPolls extends WatchedFileWithIsClosed {
unchangedPolls: number;
}
function createDynamicPriorityPollingWatchFile(host: {
getModifiedTime: NonNullable<System["getModifiedTime"]>;
setTimeout: NonNullable<System["setTimeout"]>;
}): HostWatchFile {
interface WatchedFile extends ts.WatchedFile {
isClosed?: boolean;
unchangedPolls: number;
}
interface PollingIntervalQueue extends Array<WatchedFile> {
interface PollingIntervalQueue extends Array<WatchedFileWithUnchangedPolls> {
pollingInterval: PollingInterval;
pollIndex: number;
pollScheduled: boolean;
}
const watchedFiles: WatchedFile[] = [];
const changedFilesInLastPoll: WatchedFile[] = [];
const watchedFiles: WatchedFileWithUnchangedPolls[] = [];
const changedFilesInLastPoll: WatchedFileWithUnchangedPolls[] = [];
const lowPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Low);
const mediumPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.Medium);
const highPollingIntervalQueue = createPollingIntervalQueue(PollingInterval.High);
return watchFile;
function watchFile(fileName: string, callback: FileWatcherCallback, defaultPollingInterval: PollingInterval): FileWatcher {
const file: WatchedFile = {
const file: WatchedFileWithUnchangedPolls = {
fileName,
callback,
unchangedPolls: 0,
@@ -233,7 +228,7 @@ namespace ts {
}
function createPollingIntervalQueue(pollingInterval: PollingInterval): PollingIntervalQueue {
const queue = [] as WatchedFile[] as PollingIntervalQueue;
const queue = [] as WatchedFileWithUnchangedPolls[] as PollingIntervalQueue;
queue.pollingInterval = pollingInterval;
queue.pollIndex = 0;
queue.pollScheduled = false;
@@ -265,7 +260,7 @@ namespace ts {
}
}
function pollQueue(queue: (WatchedFile | undefined)[], pollingInterval: PollingInterval, pollIndex: number, chunkSize: number) {
function pollQueue(queue: (WatchedFileWithUnchangedPolls | undefined)[], pollingInterval: PollingInterval, pollIndex: number, chunkSize: number) {
return pollWatchedFileQueue(
host,
queue,
@@ -274,7 +269,7 @@ namespace ts {
onWatchFileStat
);
function onWatchFileStat(watchedFile: WatchedFile, pollIndex: number, fileChanged: boolean) {
function onWatchFileStat(watchedFile: WatchedFileWithUnchangedPolls, pollIndex: number, fileChanged: boolean) {
if (fileChanged) {
watchedFile.unchangedPolls = 0;
// Changed files go to changedFilesInLastPoll queue
@@ -311,12 +306,12 @@ namespace ts {
}
}
function addToPollingIntervalQueue(file: WatchedFile, pollingInterval: PollingInterval) {
function addToPollingIntervalQueue(file: WatchedFileWithUnchangedPolls, pollingInterval: PollingInterval) {
pollingIntervalQueue(pollingInterval).push(file);
scheduleNextPollIfNotAlreadyScheduled(pollingInterval);
}
function addChangedFileToLowPollingIntervalQueue(file: WatchedFile) {
function addChangedFileToLowPollingIntervalQueue(file: WatchedFileWithUnchangedPolls) {
changedFilesInLastPoll.push(file);
scheduleNextPollIfNotAlreadyScheduled(PollingInterval.Low);
}
@@ -423,59 +418,50 @@ namespace ts {
}
}
/* @internal */
export function createSingleFileWatcherPerName(
watchFile: HostWatchFile,
useCaseSensitiveFileNames: boolean
): HostWatchFile {
interface SingleFileWatcher {
watcher: FileWatcher;
refCount: number;
}
const cache = new Map<string, SingleFileWatcher>();
const callbacksCache = createMultiMap<FileWatcherCallback>();
interface SingleFileWatcher<T extends FileWatcherCallback | FsWatchCallback>{
watcher: FileWatcher;
callbacks: T[];
}
function createSingleWatcherPerName<T extends FileWatcherCallback | FsWatchCallback>(
cache: Map<SingleFileWatcher<T>>,
useCaseSensitiveFileNames: boolean,
name: string,
callback: T,
createWatcher: (callback: T) => FileWatcher,
): FileWatcher {
const toCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
const path = toCanonicalFileName(name);
const existing = cache.get(path);
if (existing) {
existing.callbacks.push(callback);
}
else {
cache.set(path, {
watcher: createWatcher((
// Cant infer types correctly so lets satisfy checker
(param1: any, param2: never, param3: any) => cache.get(path)?.callbacks.slice().forEach(cb => cb(param1, param2, param3))
) as T),
callbacks: [callback]
});
}
return (fileName, callback, pollingInterval, options) => {
const path = toCanonicalFileName(fileName);
const existing = cache.get(path);
if (existing) {
existing.refCount++;
return {
close: () => {
const watcher = cache.get(path);
// Watcher is not expected to be undefined, but if it is normally its because
// exception was thrown somewhere else and watch state is not what it should be
if (!watcher) return;
if (!orderedRemoveItem(watcher.callbacks, callback) || watcher.callbacks.length) return;
cache.delete(path);
closeFileWatcherOf(watcher);
}
else {
cache.set(path, {
watcher: watchFile(
fileName,
(fileName, eventKind, modifiedTime) => forEach(
callbacksCache.get(path),
cb => cb(fileName, eventKind, modifiedTime)
),
pollingInterval,
options
),
refCount: 1
});
}
callbacksCache.add(path, callback);
return {
close: () => {
const watcher = Debug.checkDefined(cache.get(path));
callbacksCache.remove(path, callback);
watcher.refCount--;
if (watcher.refCount) return;
cache.delete(path);
closeFileWatcherOf(watcher);
}
};
};
}
/**
* Returns true if file status changed
*/
/*@internal*/
export function onWatchedFileStat(watchedFile: WatchedFile, modifiedTime: Date): boolean {
function onWatchedFileStat(watchedFile: WatchedFile, modifiedTime: Date): boolean {
const oldTime = watchedFile.mtime.getTime();
const newTime = modifiedTime.getTime();
if (oldTime !== newTime) {
@@ -512,8 +498,7 @@ namespace ts {
curSysLog = logger;
}
/*@internal*/
export interface RecursiveDirectoryWatcherHost {
interface RecursiveDirectoryWatcherHost {
watchDirectory: HostWatchDirectory;
useCaseSensitiveFileNames: boolean;
getCurrentDirectory: System["getCurrentDirectory"];
@@ -529,8 +514,7 @@ namespace ts {
* that means if this is recursive watcher, watch the children directories as well
* (eg on OS that dont support recursive watch using fs.watch use fs.watchFile)
*/
/*@internal*/
export function createDirectoryWatcherSupportingRecursive({
function createDirectoryWatcherSupportingRecursive({
watchDirectory,
useCaseSensitiveFileNames,
getCurrentDirectory,
@@ -792,8 +776,7 @@ namespace ts {
Directory,
}
/*@internal*/
export function createFileWatcherCallback(callback: FsWatchCallback): FileWatcherCallback {
function createFileWatcherCallback(callback: FsWatchCallback): FileWatcherCallback {
return (_fileName, eventKind, modifiedTime) => callback(eventKind === FileWatcherEventKind.Changed ? "change" : "rename", "", modifiedTime);
}
@@ -854,7 +837,7 @@ namespace ts {
/*@internal*/
export interface CreateSystemWatchFunctions {
// Polling watch file
pollingWatchFile: HostWatchFile;
pollingWatchFileWorker: HostWatchFile;
// For dynamic polling watch file
getModifiedTime: NonNullable<System["getModifiedTime"]>;
setTimeout: NonNullable<System["setTimeout"]>;
@@ -878,7 +861,7 @@ namespace ts {
/*@internal*/
export function createSystemWatchFunctions({
pollingWatchFile,
pollingWatchFileWorker,
getModifiedTime,
setTimeout,
clearTimeout,
@@ -896,6 +879,9 @@ namespace ts {
inodeWatching,
sysLog,
}: CreateSystemWatchFunctions): { watchFile: HostWatchFile; watchDirectory: HostWatchDirectory; } {
const pollingWatches = new Map<string, SingleFileWatcher<FileWatcherCallback>>();
const fsWatches = new Map<string, SingleFileWatcher<FsWatchCallback>>();
const fsWatchesRecursive = new Map<string, SingleFileWatcher<FsWatchCallback>>();
let dynamicPollingWatchFile: HostWatchFile | undefined;
let fixedChunkSizePollingWatchFile: HostWatchFile | undefined;
let nonPollingWatchFile: HostWatchFile | undefined;
@@ -968,7 +954,7 @@ namespace ts {
// Use notifications from FS to watch with falling back to fs.watchFile
generateWatchFileOptions(WatchFileKind.UseFsEventsOnParentDirectory, PollingWatchKind.PriorityInterval, options) :
// Default to do not use fixed polling interval
{ watchFile: defaultWatchFileKind?.() || WatchFileKind.FixedPollingInterval };
{ watchFile: defaultWatchFileKind?.() || WatchFileKind.UseFsEvents };
}
}
@@ -1073,6 +1059,15 @@ namespace ts {
}
}
function pollingWatchFile(fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined) {
return createSingleWatcherPerName(
pollingWatches,
useCaseSensitiveFileNames,
fileName,
callback,
cb => pollingWatchFileWorker(fileName, cb, pollingInterval, options),
);
}
function fsWatch(
fileOrDirectory: string,
entryKind: FileSystemEntryKind,
@@ -1080,6 +1075,23 @@ namespace ts {
recursive: boolean,
fallbackPollingInterval: PollingInterval,
fallbackOptions: WatchOptions | undefined
): FileWatcher {
return createSingleWatcherPerName(
recursive ? fsWatchesRecursive : fsWatches,
useCaseSensitiveFileNames,
fileOrDirectory,
callback,
cb => fsWatchHandlingExistenceOnHost(fileOrDirectory, entryKind, cb, recursive, fallbackPollingInterval, fallbackOptions),
);
}
function fsWatchHandlingExistenceOnHost(
fileOrDirectory: string,
entryKind: FileSystemEntryKind,
callback: FsWatchCallback,
recursive: boolean,
fallbackPollingInterval: PollingInterval,
fallbackOptions: WatchOptions | undefined
): FileWatcher {
let lastDirectoryPartWithDirectorySeparator: string | undefined;
let lastDirectoryPart: string | undefined;
@@ -1445,7 +1457,7 @@ namespace ts {
const fsSupportsRecursiveFsWatch = isNode4OrLater && (process.platform === "win32" || process.platform === "darwin");
const getCurrentDirectory = memoize(() => process.cwd());
const { watchFile, watchDirectory } = createSystemWatchFunctions({
pollingWatchFile: createSingleFileWatcherPerName(fsWatchFileWorker, useCaseSensitiveFileNames),
pollingWatchFileWorker: fsWatchFileWorker,
getModifiedTime,
setTimeout,
clearTimeout,
+10 -26
View File
@@ -283,13 +283,11 @@ interface Array<T> { length: number; [n: number]: T; }`
export interface TestFileWatcher {
cb: FileWatcherCallback;
fileName: string;
pollingInterval: PollingInterval;
}
export interface TestFsWatcher {
cb: FsWatchCallback;
directoryName: string;
inode: number | undefined;
}
@@ -310,7 +308,6 @@ interface Array<T> { length: number; [n: number]: T; }`
export enum Tsc_WatchFile {
DynamicPolling = "DynamicPriorityPolling",
SingleFileWatcherPerName = "SingleFileWatcherPerName"
}
export enum Tsc_WatchDirectory {
@@ -388,12 +385,7 @@ interface Array<T> { length: number; [n: number]: T; }`
// We dont have polling watch file
// it is essentially fsWatch but lets get that separate from fsWatch and
// into watchedFiles for easier testing
pollingWatchFile: tscWatchFile === Tsc_WatchFile.SingleFileWatcherPerName ?
createSingleFileWatcherPerName(
this.watchFileWorker.bind(this),
this.useCaseSensitiveFileNames
) :
this.watchFileWorker.bind(this),
pollingWatchFileWorker: this.watchFileWorker.bind(this),
getModifiedTime: this.getModifiedTime.bind(this),
setTimeout: this.setTimeout.bind(this),
clearTimeout: this.clearTimeout.bind(this),
@@ -498,7 +490,7 @@ interface Array<T> { length: number; [n: number]: T; }`
this.fs.get(getDirectoryPath(currentEntry.path))!.modifiedTime = this.now();
if (options && options.invokeDirectoryWatcherInsteadOfFileChanged) {
const directoryFullPath = getDirectoryPath(currentEntry.fullPath);
this.invokeFileWatcher(directoryFullPath, FileWatcherEventKind.Changed, currentEntry.modifiedTime, /*useFileNameInCallback*/ true);
this.invokeFileWatcher(directoryFullPath, FileWatcherEventKind.Changed, currentEntry.modifiedTime);
this.invokeFsWatchesCallbacks(directoryFullPath, "rename", currentEntry.modifiedTime, currentEntry.fullPath, options.useTildeAsSuffixInRenameEventFileName);
this.invokeRecursiveFsWatches(directoryFullPath, "rename", currentEntry.modifiedTime, currentEntry.fullPath, options.useTildeAsSuffixInRenameEventFileName);
}
@@ -679,7 +671,7 @@ interface Array<T> { length: number; [n: number]: T; }`
return createWatcher(
this.watchedFiles,
this.toFullPath(fileName),
{ fileName, cb, pollingInterval }
{ cb, pollingInterval }
);
}
@@ -696,7 +688,6 @@ interface Array<T> { length: number; [n: number]: T; }`
recursive ? this.fsWatchesRecursive : this.fsWatches,
path,
{
directoryName: fileOrDirectory,
cb,
inode: this.inodes?.get(path)
}
@@ -705,8 +696,8 @@ interface Array<T> { length: number; [n: number]: T; }`
return result;
}
invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind, modifiedTime?: Date, useFileNameInCallback?: boolean) {
invokeWatcherCallbacks(this.watchedFiles.get(this.toPath(fileFullPath)), ({ cb, fileName }) => cb(useFileNameInCallback ? fileName : fileFullPath, eventKind, modifiedTime));
invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind, modifiedTime: Date | undefined) {
invokeWatcherCallbacks(this.watchedFiles.get(this.toPath(fileFullPath)), ({ cb }) => cb(fileFullPath, eventKind, modifiedTime));
}
private fsWatchCallback(map: MultiMap<Path, TestFsWatcher>, fullPath: string, eventName: "rename" | "change", modifiedTime: Date | undefined, entryFullPath: string | undefined, useTildeSuffix: boolean | undefined) {
@@ -1050,11 +1041,11 @@ interface Array<T> { length: number; [n: number]: T; }`
}
serializeWatches(baseline: string[] = []) {
serializeMultiMap(baseline, "WatchedFiles", this.watchedFiles, ({ fileName, pollingInterval }) => ({ fileName, pollingInterval }));
serializeMultiMap(baseline, "PolledWatches", this.watchedFiles);
baseline.push("");
serializeMultiMap(baseline, "FsWatches", this.fsWatches, serializeTestFsWatcher);
serializeMultiMap(baseline, "FsWatches", this.fsWatches);
baseline.push("");
serializeMultiMap(baseline, "FsWatchesRecursive", this.fsWatchesRecursive, serializeTestFsWatcher);
serializeMultiMap(baseline, "FsWatchesRecursive", this.fsWatchesRecursive);
baseline.push("");
return baseline;
}
@@ -1158,19 +1149,12 @@ interface Array<T> { length: number; [n: number]: T; }`
}
}
function serializeTestFsWatcher({ directoryName, inode }: TestFsWatcher) {
return {
directoryName,
inode,
};
}
function serializeMultiMap<T, U>(baseline: string[], caption: string, multiMap: MultiMap<string, T>, valueMapper: (value: T) => U) {
function serializeMultiMap<T>(baseline: string[], caption: string, multiMap: MultiMap<string, T>) {
baseline.push(`${caption}::`);
multiMap.forEach((values, key) => {
baseline.push(`${key}:`);
for (const value of values) {
baseline.push(` ${JSON.stringify(valueMapper(value))}`);
baseline.push(` ${JSON.stringify(value)}`);
}
});
}
@@ -1,124 +1,111 @@
namespace ts.tscWatch {
describe("unittests:: tsbuildWatch:: watchEnvironment:: tsbuild:: watchMode:: with different watch environments", () => {
describe("when watchFile can create multiple watchers per file", () => {
verifyWatchFileOnMultipleProjects(/*singleWatchPerFile*/ false);
});
it("watchFile on same file multiple times because file is part of multiple projects", () => {
const project = `${TestFSWithWatch.tsbuildProjectsLocation}/myproject`;
let maxPkgs = 4;
const configPath = `${project}/tsconfig.json`;
const typing: File = {
path: `${project}/typings/xterm.d.ts`,
content: "export const typing = 10;"
};
describe("when watchFile is single watcher per file", () => {
verifyWatchFileOnMultipleProjects(
/*singleWatchPerFile*/ true,
arrayToMap(["TSC_WATCHFILE"], identity, () => TestFSWithWatch.Tsc_WatchFile.SingleFileWatcherPerName)
);
});
function verifyWatchFileOnMultipleProjects(singleWatchPerFile: boolean, environmentVariables?: ESMap<string, string>) {
it("watchFile on same file multiple times because file is part of multiple projects", () => {
const project = `${TestFSWithWatch.tsbuildProjectsLocation}/myproject`;
let maxPkgs = 4;
const configPath = `${project}/tsconfig.json`;
const typing: File = {
path: `${project}/typings/xterm.d.ts`,
content: "export const typing = 10;"
};
const allPkgFiles = pkgs(pkgFiles);
const system = createWatchedSystem([libFile, typing, ...flatArray(allPkgFiles)], { currentDirectory: project, environmentVariables });
writePkgReferences(system);
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(system);
const host = createSolutionBuilderWithWatchHostForBaseline(sys, cb);
const solutionBuilder = createSolutionBuilderWithWatch(host, ["tsconfig.json"], { watch: true, verbose: true });
solutionBuilder.build();
runWatchBaseline({
scenario: "watchEnvironment",
subScenario: `same file in multiple projects${singleWatchPerFile ? " with single watcher per file" : ""}`,
commandLineArgs: ["--b", "--w"],
sys,
baseline,
oldSnap,
getPrograms,
changes: [
{
caption: "modify typing file",
change: sys => sys.writeFile(typing.path, `${typing.content}export const typing1 = 10;`),
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1);
checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout(sys);
}
},
{
// Make change
caption: "change pkg references",
change: sys => {
maxPkgs--;
writePkgReferences(sys);
},
timeouts: checkSingleTimeoutQueueLengthAndRun,
},
{
caption: "modify typing file",
change: sys => sys.writeFile(typing.path, typing.content),
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1);
checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout(sys);
}
},
{
// Make change to remove all watches
caption: "change pkg references to remove all watches",
change: sys => {
maxPkgs = 0;
writePkgReferences(sys);
},
timeouts: checkSingleTimeoutQueueLengthAndRun,
},
{
caption: "modify typing file",
change: sys => sys.writeFile(typing.path, `${typing.content}export const typing1 = 10;`),
timeouts: sys => sys.checkTimeoutQueueLength(0),
},
],
watchOrSolution: solutionBuilder
});
function flatArray<T>(arr: T[][]): readonly T[] {
return flatMap(arr, identity);
}
function pkgs<T>(cb: (index: number) => T): T[] {
const result: T[] = [];
for (let index = 0; index < maxPkgs; index++) {
result.push(cb(index));
}
return result;
}
function createPkgReference(index: number) {
return { path: `./pkg${index}` };
}
function pkgFiles(index: number): File[] {
return [
{
path: `${project}/pkg${index}/index.ts`,
content: `export const pkg${index} = ${index};`
},
{
path: `${project}/pkg${index}/tsconfig.json`,
content: JSON.stringify({
complerOptions: { composite: true },
include: [
"**/*.ts",
"../typings/xterm.d.ts"
]
})
const allPkgFiles = pkgs(pkgFiles);
const system = createWatchedSystem([libFile, typing, ...flatArray(allPkgFiles)], { currentDirectory: project });
writePkgReferences(system);
const { sys, baseline, oldSnap, cb, getPrograms } = createBaseline(system);
const host = createSolutionBuilderWithWatchHostForBaseline(sys, cb);
const solutionBuilder = createSolutionBuilderWithWatch(host, ["tsconfig.json"], { watch: true, verbose: true });
solutionBuilder.build();
runWatchBaseline({
scenario: "watchEnvironment",
subScenario: `same file in multiple projects with single watcher per file`,
commandLineArgs: ["--b", "--w"],
sys,
baseline,
oldSnap,
getPrograms,
changes: [
{
caption: "modify typing file",
change: sys => sys.writeFile(typing.path, `${typing.content}export const typing1 = 10;`),
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1);
checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout(sys);
}
];
}
function writePkgReferences(system: TestFSWithWatch.TestServerHost) {
system.writeFile(configPath, JSON.stringify({
files: [],
include: [],
references: pkgs(createPkgReference)
}));
}
},
{
// Make change
caption: "change pkg references",
change: sys => {
maxPkgs--;
writePkgReferences(sys);
},
timeouts: checkSingleTimeoutQueueLengthAndRun,
},
{
caption: "modify typing file",
change: sys => sys.writeFile(typing.path, typing.content),
timeouts: sys => {
sys.checkTimeoutQueueLengthAndRun(1);
checkSingleTimeoutQueueLengthAndRunAndVerifyNoTimeout(sys);
}
},
{
// Make change to remove all watches
caption: "change pkg references to remove all watches",
change: sys => {
maxPkgs = 0;
writePkgReferences(sys);
},
timeouts: checkSingleTimeoutQueueLengthAndRun,
},
{
caption: "modify typing file",
change: sys => sys.writeFile(typing.path, `${typing.content}export const typing1 = 10;`),
timeouts: sys => sys.checkTimeoutQueueLength(0),
},
],
watchOrSolution: solutionBuilder
});
}
function flatArray<T>(arr: T[][]): readonly T[] {
return flatMap(arr, identity);
}
function pkgs<T>(cb: (index: number) => T): T[] {
const result: T[] = [];
for (let index = 0; index < maxPkgs; index++) {
result.push(cb(index));
}
return result;
}
function createPkgReference(index: number) {
return { path: `./pkg${index}` };
}
function pkgFiles(index: number): File[] {
return [
{
path: `${project}/pkg${index}/index.ts`,
content: `export const pkg${index} = ${index};`
},
{
path: `${project}/pkg${index}/tsconfig.json`,
content: JSON.stringify({
complerOptions: { composite: true },
include: [
"**/*.ts",
"../typings/xterm.d.ts"
]
})
}
];
}
function writePkgReferences(system: TestFSWithWatch.TestServerHost) {
system.writeFile(configPath, JSON.stringify({
files: [],
include: [],
references: pkgs(createPkgReference)
}));
}
});
});
}
@@ -499,33 +499,25 @@ namespace ts.projectSystem {
});
describe("unittests:: tsserver:: watchEnvironment:: watchFile is single watcher per file", () => {
function verifyWatchFile(scenario: string, environmentVariables?: ESMap<string, string>) {
it(scenario, () => {
const config: File = {
path: `${tscWatch.projectRoot}/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
composite: true,
resolveJsonModule: true,
},
})
};
const index: File = {
path: `${tscWatch.projectRoot}/index.ts`,
content: `import * as tsconfig from "./tsconfig.json";`
};
const host = createServerHost([config, index, libFile], { environmentVariables });
const session = createSession(host, { logger: createLoggerWithInMemoryLogs() });
openFilesForSession([index], session);
host.serializeWatches().forEach(b => session.logger.info(b));
baselineTsserverLogs("watchEnvironment", scenario, session);
});
}
verifyWatchFile("when watchFile can create multiple watchers per file");
verifyWatchFile(
"when watchFile is single watcher per file",
arrayToMap(["TSC_WATCHFILE"], identity, () => TestFSWithWatch.Tsc_WatchFile.SingleFileWatcherPerName)
);
it("when watchFile is single watcher per file", () => {
const config: File = {
path: `${tscWatch.projectRoot}/tsconfig.json`,
content: JSON.stringify({
compilerOptions: {
composite: true,
resolveJsonModule: true,
},
})
};
const index: File = {
path: `${tscWatch.projectRoot}/index.ts`,
content: `import * as tsconfig from "./tsconfig.json";`
};
const host = createServerHost([config, index, libFile]);
const session = createSession(host, { logger: createLoggerWithInMemoryLogs() });
openFilesForSession([index], session);
host.serializeWatches().forEach(b => session.logger.info(b));
baselineTsserverLogs("watchEnvironment", "when watchFile is single watcher per file", session);
});
});
}
-14
View File
@@ -247,20 +247,6 @@ namespace ts.server {
// Override sys.write because fs.writeSync is not reliable on Node 4
sys.write = (s: string) => writeMessage(sys.bufferFrom!(s, "utf8") as globalThis.Buffer);
// REVIEW: for now this implementation uses polling.
// The advantage of polling is that it works reliably
// on all os and with network mounted files.
// For 90 referenced files, the average time to detect
// changes is 2*msInterval (by default 5 seconds).
// The overhead of this is .04 percent (1/2500) with
// average pause of < 1 millisecond (and max
// pause less than 1.5 milliseconds); question is
// do we anticipate reference sets in the 100s and
// do we care about waiting 10-20 seconds to detect
// changes for large reference sets? If so, do we want
// to increase the chunk size or decrease the interval
// time dynamically to match the large reference set?
sys.defaultWatchFileKind = () => WatchFileKind.FixedChunkSizePolling;
/* eslint-disable no-restricted-globals */
sys.setTimeout = setTimeout;