mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-30 01:04:49 -05:00
Merge pull request #32613 from microsoft/singleHostFsWatchFile
Create only single StatFileWatcher through node
This commit is contained in:
@@ -304,6 +304,53 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function createSingleFileWatcherPerName(
|
||||
watchFile: HostWatchFile,
|
||||
useCaseSensitiveFileNames: boolean
|
||||
): HostWatchFile {
|
||||
interface SingleFileWatcher {
|
||||
watcher: FileWatcher;
|
||||
refCount: number;
|
||||
}
|
||||
const cache = createMap<SingleFileWatcher>();
|
||||
const callbacksCache = createMultiMap<FileWatcherCallback>();
|
||||
const toCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
|
||||
|
||||
return (fileName, callback, pollingInterval) => {
|
||||
const path = toCanonicalFileName(fileName);
|
||||
const existing = cache.get(path);
|
||||
if (existing) {
|
||||
existing.refCount++;
|
||||
}
|
||||
else {
|
||||
cache.set(path, {
|
||||
watcher: watchFile(
|
||||
fileName,
|
||||
(fileName, eventKind) => forEach(
|
||||
callbacksCache.get(path),
|
||||
cb => cb(fileName, eventKind)
|
||||
),
|
||||
pollingInterval
|
||||
),
|
||||
refCount: 1
|
||||
});
|
||||
}
|
||||
callbacksCache.add(path, callback);
|
||||
|
||||
return {
|
||||
close: () => {
|
||||
const watcher = Debug.assertDefined(cache.get(path));
|
||||
callbacksCache.remove(path, callback);
|
||||
watcher.refCount--;
|
||||
if (watcher.refCount) return;
|
||||
cache.delete(path);
|
||||
closeFileWatcherOf(watcher);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if file status changed
|
||||
*/
|
||||
@@ -695,6 +742,7 @@ namespace ts {
|
||||
const useNonPollingWatchers = process.env.TSC_NONPOLLING_WATCHER;
|
||||
const tscWatchFile = process.env.TSC_WATCHFILE;
|
||||
const tscWatchDirectory = process.env.TSC_WATCHDIRECTORY;
|
||||
const fsWatchFile = createSingleFileWatcherPerName(fsWatchFileWorker, useCaseSensitiveFileNames);
|
||||
let dynamicPollingWatchFile: HostWatchFile | undefined;
|
||||
const nodeSystem: System = {
|
||||
args: process.argv.slice(2),
|
||||
@@ -835,7 +883,7 @@ namespace ts {
|
||||
return useNonPollingWatchers ?
|
||||
createNonPollingWatchFile() :
|
||||
// Default to do not use polling interval as it is before this experiment branch
|
||||
(fileName, callback) => fsWatchFile(fileName, callback);
|
||||
(fileName, callback) => fsWatchFile(fileName, callback, /*pollingInterval*/ undefined);
|
||||
}
|
||||
|
||||
function getWatchDirectory(): HostWatchDirectory {
|
||||
@@ -916,7 +964,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function fsWatchFile(fileName: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher {
|
||||
function fsWatchFileWorker(fileName: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher {
|
||||
_fs.watchFile(fileName, { persistent: true, interval: pollingInterval || 250 }, fileChanged);
|
||||
let eventKind: FileWatcherEventKind;
|
||||
return {
|
||||
|
||||
@@ -564,8 +564,51 @@ namespace ts {
|
||||
}
|
||||
|
||||
function getBuildOrder(state: SolutionBuilderState) {
|
||||
return state.buildOrder ||
|
||||
(state.buildOrder = createBuildOrder(state, state.rootNames.map(f => resolveProjectName(state, f))));
|
||||
return state.buildOrder || createStateBuildOrder(state);
|
||||
}
|
||||
|
||||
function createStateBuildOrder(state: SolutionBuilderState) {
|
||||
const buildOrder = createBuildOrder(state, state.rootNames.map(f => resolveProjectName(state, f)));
|
||||
if (arrayIsEqualTo(state.buildOrder, buildOrder)) return state.buildOrder!;
|
||||
|
||||
// Clear all to ResolvedConfigFilePaths cache to start fresh
|
||||
state.resolvedConfigFilePaths.clear();
|
||||
const currentProjects = arrayToSet(
|
||||
buildOrder,
|
||||
resolved => toResolvedConfigFilePath(state, resolved)
|
||||
) as ConfigFileMap<true>;
|
||||
|
||||
const noopOnDelete = { onDeleteValue: noop };
|
||||
// Config file cache
|
||||
mutateMapSkippingNewValues(state.configFileCache, currentProjects, noopOnDelete);
|
||||
mutateMapSkippingNewValues(state.projectStatus, currentProjects, noopOnDelete);
|
||||
mutateMapSkippingNewValues(state.buildInfoChecked, currentProjects, noopOnDelete);
|
||||
mutateMapSkippingNewValues(state.builderPrograms, currentProjects, noopOnDelete);
|
||||
mutateMapSkippingNewValues(state.diagnostics, currentProjects, noopOnDelete);
|
||||
mutateMapSkippingNewValues(state.projectPendingBuild, currentProjects, noopOnDelete);
|
||||
mutateMapSkippingNewValues(state.projectErrorsReported, currentProjects, noopOnDelete);
|
||||
|
||||
// Remove watches for the program no longer in the solution
|
||||
if (state.watch) {
|
||||
mutateMapSkippingNewValues(
|
||||
state.allWatchedConfigFiles,
|
||||
currentProjects,
|
||||
{ onDeleteValue: closeFileWatcher }
|
||||
);
|
||||
|
||||
mutateMapSkippingNewValues(
|
||||
state.allWatchedWildcardDirectories,
|
||||
currentProjects,
|
||||
{ onDeleteValue: existingMap => existingMap.forEach(closeFileWatcherOf) }
|
||||
);
|
||||
|
||||
mutateMapSkippingNewValues(
|
||||
state.allWatchedInputFiles,
|
||||
currentProjects,
|
||||
{ onDeleteValue: existingMap => existingMap.forEach(closeFileWatcher) }
|
||||
);
|
||||
}
|
||||
return state.buildOrder = buildOrder;
|
||||
}
|
||||
|
||||
function getBuildOrderFor(state: SolutionBuilderState, project: string | undefined, onlyReferences: boolean | undefined) {
|
||||
|
||||
@@ -4466,8 +4466,7 @@ namespace ts {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
export interface MutateMapOptions<T, U> {
|
||||
createNewValue(key: string, valueInNewMap: U): T;
|
||||
export interface MutateMapSkippingNewValuesOptions<T, U> {
|
||||
onDeleteValue(existingValue: T, key: string): void;
|
||||
|
||||
/**
|
||||
@@ -4482,8 +4481,12 @@ namespace ts {
|
||||
/**
|
||||
* Mutates the map with newMap such that keys in map will be same as newMap.
|
||||
*/
|
||||
export function mutateMap<T, U>(map: Map<T>, newMap: ReadonlyMap<U>, options: MutateMapOptions<T, U>) {
|
||||
const { createNewValue, onDeleteValue, onExistingValue } = options;
|
||||
export function mutateMapSkippingNewValues<T, U>(
|
||||
map: Map<T>,
|
||||
newMap: ReadonlyMap<U>,
|
||||
options: MutateMapSkippingNewValuesOptions<T, U>
|
||||
) {
|
||||
const { onDeleteValue, onExistingValue } = options;
|
||||
// Needs update
|
||||
map.forEach((existingValue, key) => {
|
||||
const valueInNewMap = newMap.get(key);
|
||||
@@ -4497,7 +4500,20 @@ namespace ts {
|
||||
onExistingValue(existingValue, valueInNewMap, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export interface MutateMapOptions<T, U> extends MutateMapSkippingNewValuesOptions<T, U> {
|
||||
createNewValue(key: string, valueInNewMap: U): T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutates the map with newMap such that keys in map will be same as newMap.
|
||||
*/
|
||||
export function mutateMap<T, U>(map: Map<T>, newMap: ReadonlyMap<U>, options: MutateMapOptions<T, U>) {
|
||||
// Needs update
|
||||
mutateMapSkippingNewValues(map, newMap, options);
|
||||
|
||||
const { createNewValue } = options;
|
||||
// Add new values that are not already present
|
||||
newMap.forEach((valueInNewMap, key) => {
|
||||
if (!map.has(key)) {
|
||||
|
||||
@@ -314,6 +314,11 @@ interface Array<T> {}`
|
||||
invokeFileDeleteCreateAsPartInsteadOfChange: boolean;
|
||||
}
|
||||
|
||||
export enum Tsc_WatchFile {
|
||||
DynamicPolling = "DynamicPriorityPolling",
|
||||
SingleFileWatcherPerName = "SingleFileWatcherPerName"
|
||||
}
|
||||
|
||||
export enum Tsc_WatchDirectory {
|
||||
WatchFile = "RecursiveDirectoryUsingFsWatchFile",
|
||||
NonRecursiveWatchDirectory = "RecursiveDirectoryUsingNonRecursiveWatchDirectory",
|
||||
@@ -339,7 +344,7 @@ interface Array<T> {}`
|
||||
readonly watchedFiles = createMultiMap<TestFileWatcher>();
|
||||
private readonly executingFilePath: string;
|
||||
private readonly currentDirectory: string;
|
||||
private readonly dynamicPriorityWatchFile: HostWatchFile | undefined;
|
||||
private readonly customWatchFile: HostWatchFile | undefined;
|
||||
private readonly customRecursiveWatchDirectory: HostWatchDirectory | undefined;
|
||||
public require: ((initialPath: string, moduleName: string) => server.RequireResult) | undefined;
|
||||
|
||||
@@ -349,9 +354,23 @@ interface Array<T> {}`
|
||||
this.executingFilePath = this.getHostSpecificPath(executingFilePath);
|
||||
this.currentDirectory = this.getHostSpecificPath(currentDirectory);
|
||||
this.reloadFS(fileOrFolderorSymLinkList);
|
||||
this.dynamicPriorityWatchFile = this.environmentVariables && this.environmentVariables.get("TSC_WATCHFILE") === "DynamicPriorityPolling" ?
|
||||
createDynamicPriorityPollingWatchFile(this) :
|
||||
undefined;
|
||||
const tscWatchFile = this.environmentVariables && this.environmentVariables.get("TSC_WATCHFILE") as Tsc_WatchFile;
|
||||
switch (tscWatchFile) {
|
||||
case Tsc_WatchFile.DynamicPolling:
|
||||
this.customWatchFile = createDynamicPriorityPollingWatchFile(this);
|
||||
break;
|
||||
case Tsc_WatchFile.SingleFileWatcherPerName:
|
||||
this.customWatchFile = createSingleFileWatcherPerName(
|
||||
this.watchFileWorker.bind(this),
|
||||
this.useCaseSensitiveFileNames
|
||||
);
|
||||
break;
|
||||
case undefined:
|
||||
break;
|
||||
default:
|
||||
Debug.assertNever(tscWatchFile);
|
||||
}
|
||||
|
||||
const tscWatchDirectory = this.environmentVariables && this.environmentVariables.get("TSC_WATCHDIRECTORY") as Tsc_WatchDirectory;
|
||||
if (tscWatchDirectory === Tsc_WatchDirectory.WatchFile) {
|
||||
const watchDirectory: HostWatchDirectory = (directory, cb) => this.watchFile(directory, () => cb(directory), PollingInterval.Medium);
|
||||
@@ -405,7 +424,7 @@ interface Array<T> {}`
|
||||
return s;
|
||||
}
|
||||
|
||||
private now() {
|
||||
now() {
|
||||
this.time += timeIncrements;
|
||||
return new Date(this.time);
|
||||
}
|
||||
@@ -854,10 +873,14 @@ interface Array<T> {}`
|
||||
}
|
||||
|
||||
watchFile(fileName: string, cb: FileWatcherCallback, pollingInterval: number) {
|
||||
if (this.dynamicPriorityWatchFile) {
|
||||
return this.dynamicPriorityWatchFile(fileName, cb, pollingInterval);
|
||||
if (this.customWatchFile) {
|
||||
return this.customWatchFile(fileName, cb, pollingInterval);
|
||||
}
|
||||
|
||||
return this.watchFileWorker(fileName, cb);
|
||||
}
|
||||
|
||||
private watchFileWorker(fileName: string, cb: FileWatcherCallback) {
|
||||
const path = this.toFullPath(fileName);
|
||||
const callback: TestFileWatcher = { fileName, cb };
|
||||
this.watchedFiles.add(path, callback);
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
"unittests/semver.ts",
|
||||
"unittests/shimMap.ts",
|
||||
"unittests/transform.ts",
|
||||
"unittests/tsbuildWatchMode.ts",
|
||||
"unittests/config/commandLineParsing.ts",
|
||||
"unittests/config/configurationExtension.ts",
|
||||
"unittests/config/convertCompilerOptionsFromJson.ts",
|
||||
@@ -103,6 +102,8 @@
|
||||
"unittests/tsbuild/resolveJsonModule.ts",
|
||||
"unittests/tsbuild/sample.ts",
|
||||
"unittests/tsbuild/transitiveReferences.ts",
|
||||
"unittests/tsbuild/watchEnvironment.ts",
|
||||
"unittests/tsbuild/watchMode.ts",
|
||||
"unittests/tscWatch/consoleClearing.ts",
|
||||
"unittests/tscWatch/emit.ts",
|
||||
"unittests/tscWatch/emitAndErrorUpdates.ts",
|
||||
|
||||
124
src/testRunner/unittests/tsbuild/watchEnvironment.ts
Normal file
124
src/testRunner/unittests/tsbuild/watchEnvironment.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
namespace ts.tscWatch {
|
||||
describe("unittests:: tsbuild:: watchEnvironment:: tsbuild:: watchMode:: with different watch environments", () => {
|
||||
describe("when watchFile can create multiple watchers per file", () => {
|
||||
verifyWatchFileOnMultipleProjects(/*singleWatchPerFile*/ false);
|
||||
});
|
||||
|
||||
describe("when watchFile is single watcher per file", () => {
|
||||
verifyWatchFileOnMultipleProjects(
|
||||
/*singleWatchPerFile*/ true,
|
||||
arrayToMap(["TSC_WATCHFILE"], identity, () => TestFSWithWatch.Tsc_WatchFile.SingleFileWatcherPerName)
|
||||
);
|
||||
});
|
||||
|
||||
function verifyWatchFileOnMultipleProjects(singleWatchPerFile: boolean, environmentVariables?: Map<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();
|
||||
const host = createSolutionBuilderWithWatchHost(system);
|
||||
const solutionBuilder = createSolutionBuilderWithWatch(host, ["tsconfig.json"], { watch: true, verbose: true });
|
||||
solutionBuilder.build();
|
||||
checkOutputErrorsInitial(system, emptyArray, /*disableConsoleClears*/ undefined, [
|
||||
`Projects in this build: \r\n${
|
||||
concatenate(
|
||||
pkgs(index => ` * pkg${index}/tsconfig.json`),
|
||||
[" * tsconfig.json"]
|
||||
).join("\r\n")}\n\n`,
|
||||
...flatArray(pkgs(index => [
|
||||
`Project 'pkg${index}/tsconfig.json' is out of date because output file 'pkg${index}/index.js' does not exist\n\n`,
|
||||
`Building project '${project}/pkg${index}/tsconfig.json'...\n\n`
|
||||
]))
|
||||
]);
|
||||
|
||||
const watchFilesDetailed = arrayToMap(flatArray(allPkgFiles), f => f.path, () => 1);
|
||||
watchFilesDetailed.set(configPath, 1);
|
||||
watchFilesDetailed.set(typing.path, singleWatchPerFile ? 1 : maxPkgs);
|
||||
checkWatchedFilesDetailed(system, watchFilesDetailed);
|
||||
system.writeFile(typing.path, `${typing.content}export const typing1 = 10;`);
|
||||
verifyInvoke();
|
||||
|
||||
// Make change
|
||||
maxPkgs--;
|
||||
writePkgReferences();
|
||||
system.checkTimeoutQueueLengthAndRun(1);
|
||||
checkOutputErrorsIncremental(system, emptyArray);
|
||||
const lastFiles = last(allPkgFiles);
|
||||
lastFiles.forEach(f => watchFilesDetailed.delete(f.path));
|
||||
watchFilesDetailed.set(typing.path, singleWatchPerFile ? 1 : maxPkgs);
|
||||
checkWatchedFilesDetailed(system, watchFilesDetailed);
|
||||
system.writeFile(typing.path, typing.content);
|
||||
verifyInvoke();
|
||||
|
||||
// Make change to remove all the watches
|
||||
maxPkgs = 0;
|
||||
writePkgReferences();
|
||||
system.checkTimeoutQueueLengthAndRun(1);
|
||||
checkOutputErrorsIncremental(system, [
|
||||
`tsconfig.json(1,10): error TS18002: The 'files' list in config file '${configPath}' is empty.\n`
|
||||
]);
|
||||
checkWatchedFilesDetailed(system, [configPath], 1);
|
||||
|
||||
system.writeFile(typing.path, `${typing.content}export const typing1 = 10;`);
|
||||
system.checkTimeoutQueueLength(0);
|
||||
|
||||
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.writeFile(configPath, JSON.stringify({
|
||||
files: [],
|
||||
include: [],
|
||||
references: pkgs(createPkgReference)
|
||||
}));
|
||||
}
|
||||
function verifyInvoke() {
|
||||
pkgs(() => system.checkTimeoutQueueLengthAndRun(1));
|
||||
checkOutputErrorsIncremental(system, emptyArray, /*disableConsoleClears*/ undefined, /*logsBeforeWatchDiagnostics*/ undefined, [
|
||||
...flatArray(pkgs(index => [
|
||||
`Project 'pkg${index}/tsconfig.json' is out of date because oldest output 'pkg${index}/index.js' is older than newest input 'typings/xterm.d.ts'\n\n`,
|
||||
`Building project '${project}/pkg${index}/tsconfig.json'...\n\n`,
|
||||
`Updating unchanged output timestamps of project '${project}/pkg${index}/tsconfig.json'...\n\n`
|
||||
]))
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -16,11 +16,19 @@ namespace ts.tscWatch {
|
||||
return host;
|
||||
}
|
||||
|
||||
|
||||
export function createSolutionBuilder(system: WatchedSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
|
||||
const host = createSolutionBuilderHost(system);
|
||||
host.now = system.now.bind(system);
|
||||
return ts.createSolutionBuilder(host, rootNames, defaultOptions || {});
|
||||
}
|
||||
|
||||
export function createSolutionBuilderWithWatchHost(system: WatchedSystem) {
|
||||
const host = ts.createSolutionBuilderWithWatchHost(system);
|
||||
host.now = system.now.bind(system);
|
||||
return host;
|
||||
}
|
||||
|
||||
function createSolutionBuilderWithWatch(system: TsBuildWatchSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
|
||||
const host = createSolutionBuilderWithWatchHost(system);
|
||||
const solutionBuilder = ts.createSolutionBuilderWithWatch(host, rootNames, defaultOptions || { watch: true });
|
||||
@@ -33,7 +41,7 @@ namespace ts.tscWatch {
|
||||
return [f, host.getModifiedTime(f), host.writtenFiles.has(host.toFullPath(f))] as OutputFileStamp;
|
||||
}
|
||||
|
||||
describe("unittests:: tsbuild-watch program updates", () => {
|
||||
describe("unittests:: tsbuild:: watchMode:: program updates", () => {
|
||||
const project = "sample1";
|
||||
const enum SubProject {
|
||||
core = "core",
|
||||
@@ -113,13 +121,33 @@ namespace ts.tscWatch {
|
||||
}
|
||||
}
|
||||
|
||||
const core = subProjectFiles(SubProject.core, /*anotherModuleAndSomeDecl*/ true);
|
||||
const logic = subProjectFiles(SubProject.logic);
|
||||
const tests = subProjectFiles(SubProject.tests);
|
||||
const ui = subProjectFiles(SubProject.ui);
|
||||
const allFiles: ReadonlyArray<File> = [libFile, ...core, ...logic, ...tests, ...ui];
|
||||
const testProjectExpectedWatchedFiles = [core[0], core[1], core[2]!, ...logic, ...tests].map(f => f.path.toLowerCase()); // tslint:disable-line no-unnecessary-type-assertion (TODO: type assertion should be necessary)
|
||||
const testProjectExpectedWatchedDirectoriesRecursive = [projectPath(SubProject.core), projectPath(SubProject.logic)];
|
||||
let core: SubProjectFiles;
|
||||
let logic: SubProjectFiles;
|
||||
let tests: SubProjectFiles;
|
||||
let ui: SubProjectFiles;
|
||||
let allFiles: ReadonlyArray<File>;
|
||||
let testProjectExpectedWatchedFiles: string[];
|
||||
let testProjectExpectedWatchedDirectoriesRecursive: string[];
|
||||
|
||||
before(() => {
|
||||
core = subProjectFiles(SubProject.core, /*anotherModuleAndSomeDecl*/ true);
|
||||
logic = subProjectFiles(SubProject.logic);
|
||||
tests = subProjectFiles(SubProject.tests);
|
||||
ui = subProjectFiles(SubProject.ui);
|
||||
allFiles = [libFile, ...core, ...logic, ...tests, ...ui];
|
||||
testProjectExpectedWatchedFiles = [core[0], core[1], core[2]!, ...logic, ...tests].map(f => f.path.toLowerCase()); // tslint:disable-line no-unnecessary-type-assertion (TODO: type assertion should be necessary)
|
||||
testProjectExpectedWatchedDirectoriesRecursive = [projectPath(SubProject.core), projectPath(SubProject.logic)];
|
||||
});
|
||||
|
||||
after(() => {
|
||||
core = undefined!;
|
||||
logic = undefined!;
|
||||
tests = undefined!;
|
||||
ui = undefined!;
|
||||
allFiles = undefined!;
|
||||
testProjectExpectedWatchedFiles = undefined!;
|
||||
testProjectExpectedWatchedDirectoriesRecursive = undefined!;
|
||||
});
|
||||
|
||||
function createSolutionInWatchMode(allFiles: ReadonlyArray<File>, defaultOptions?: BuildOptions, disableConsoleClears?: boolean) {
|
||||
const host = createTsBuildWatchSystem(allFiles, { currentDirectory: projectsLocation });
|
||||
@@ -172,9 +200,9 @@ namespace ts.tscWatch {
|
||||
content: `export const newFileConst = 30;`
|
||||
};
|
||||
|
||||
function verifyProjectChanges(allFiles: ReadonlyArray<File>) {
|
||||
function verifyProjectChanges(allFilesGetter: () => ReadonlyArray<File>) {
|
||||
function createSolutionInWatchModeToVerifyChanges(additionalFiles?: ReadonlyArray<[SubProject, string]>) {
|
||||
const host = createSolutionInWatchMode(allFiles);
|
||||
const host = createSolutionInWatchMode(allFilesGetter());
|
||||
return { host, verifyChangeWithFile, verifyChangeAfterTimeout, verifyWatches };
|
||||
|
||||
function verifyChangeWithFile(fileName: string, content: string, local?: boolean) {
|
||||
@@ -277,19 +305,21 @@ export class someClass2 { }`);
|
||||
}
|
||||
|
||||
describe("with simple project reference graph", () => {
|
||||
verifyProjectChanges(allFiles);
|
||||
verifyProjectChanges(() => allFiles);
|
||||
});
|
||||
|
||||
describe("with circular project reference", () => {
|
||||
const [coreTsconfig, ...otherCoreFiles] = core;
|
||||
const circularCoreConfig: File = {
|
||||
path: coreTsconfig.path,
|
||||
content: JSON.stringify({
|
||||
compilerOptions: { composite: true, declaration: true },
|
||||
references: [{ path: "../tests", circular: true }]
|
||||
})
|
||||
};
|
||||
verifyProjectChanges([libFile, circularCoreConfig, ...otherCoreFiles, ...logic, ...tests]);
|
||||
verifyProjectChanges(() => {
|
||||
const [coreTsconfig, ...otherCoreFiles] = core;
|
||||
const circularCoreConfig: File = {
|
||||
path: coreTsconfig.path,
|
||||
content: JSON.stringify({
|
||||
compilerOptions: { composite: true, declaration: true },
|
||||
references: [{ path: "../tests", circular: true }]
|
||||
})
|
||||
};
|
||||
return [libFile, circularCoreConfig, ...otherCoreFiles, ...logic, ...tests];
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -681,9 +711,9 @@ let x: string = 10;`);
|
||||
const coreIndexDts = projectFileName(SubProject.core, "index.d.ts");
|
||||
const coreAnotherModuleDts = projectFileName(SubProject.core, "anotherModule.d.ts");
|
||||
const logicIndexDts = projectFileName(SubProject.logic, "index.d.ts");
|
||||
const expectedWatchedFiles = [core[0], logic[0], ...tests, libFile].map(f => f.path).concat([coreIndexDts, coreAnotherModuleDts, logicIndexDts].map(f => f.toLowerCase()));
|
||||
const expectedWatchedFiles = () => [core[0], logic[0], ...tests, libFile].map(f => f.path).concat([coreIndexDts, coreAnotherModuleDts, logicIndexDts].map(f => f.toLowerCase()));
|
||||
const expectedWatchedDirectoriesRecursive = projectSystem.getTypeRootsFromLocation(projectPath(SubProject.tests));
|
||||
const expectedProgramFiles = [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, logicIndexDts];
|
||||
const expectedProgramFiles = () => [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, logicIndexDts];
|
||||
|
||||
function createSolutionAndWatchMode() {
|
||||
return createSolutionAndWatchModeOfProject(allFiles, projectsLocation, `${project}/${SubProject.tests}`, tests[0].path, getOutputFileStamps);
|
||||
@@ -694,12 +724,12 @@ let x: string = 10;`);
|
||||
}
|
||||
|
||||
function verifyWatches(host: TsBuildWatchSystem, withTsserver?: boolean) {
|
||||
verifyWatchesOfProject(host, withTsserver ? expectedWatchedFiles.filter(f => f !== tests[1].path.toLowerCase()) : expectedWatchedFiles, expectedWatchedDirectoriesRecursive);
|
||||
verifyWatchesOfProject(host, withTsserver ? expectedWatchedFiles().filter(f => f !== tests[1].path.toLowerCase()) : expectedWatchedFiles(), expectedWatchedDirectoriesRecursive);
|
||||
}
|
||||
|
||||
function verifyScenario(
|
||||
edit: (host: TsBuildWatchSystem, solutionBuilder: SolutionBuilder<EmitAndSemanticDiagnosticsBuilderProgram>) => void,
|
||||
expectedFilesAfterEdit: ReadonlyArray<string>
|
||||
expectedFilesAfterEdit: () => ReadonlyArray<string>
|
||||
) {
|
||||
it("with tsc-watch", () => {
|
||||
const { host, solutionBuilder, watch } = createSolutionAndWatchMode();
|
||||
@@ -708,7 +738,7 @@ let x: string = 10;`);
|
||||
|
||||
host.checkTimeoutQueueLengthAndRun(1);
|
||||
checkOutputErrorsIncremental(host, emptyArray);
|
||||
checkProgramActualFiles(watch(), expectedFilesAfterEdit);
|
||||
checkProgramActualFiles(watch(), expectedFilesAfterEdit());
|
||||
|
||||
});
|
||||
|
||||
@@ -718,7 +748,7 @@ let x: string = 10;`);
|
||||
edit(host, solutionBuilder);
|
||||
|
||||
host.checkTimeoutQueueLengthAndRun(2);
|
||||
checkProjectActualFiles(service, tests[0].path, [tests[0].path, ...expectedFilesAfterEdit]);
|
||||
checkProjectActualFiles(service, tests[0].path, [tests[0].path, ...expectedFilesAfterEdit()]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -729,7 +759,7 @@ let x: string = 10;`);
|
||||
verifyDependencies(watch, coreIndexDts, [coreIndexDts]);
|
||||
verifyDependencies(watch, coreAnotherModuleDts, [coreAnotherModuleDts]);
|
||||
verifyDependencies(watch, logicIndexDts, [logicIndexDts, coreAnotherModuleDts]);
|
||||
verifyDependencies(watch, tests[1].path, expectedProgramFiles.filter(f => f !== libFile.path));
|
||||
verifyDependencies(watch, tests[1].path, expectedProgramFiles().filter(f => f !== libFile.path));
|
||||
});
|
||||
|
||||
it("with tsserver", () => {
|
||||
@@ -769,7 +799,7 @@ export function gfoo() {
|
||||
}));
|
||||
solutionBuilder.invalidateProject(logic[0].path.toLowerCase() as ResolvedConfigFilePath, ConfigFileProgramReloadLevel.Full);
|
||||
solutionBuilder.buildNextInvalidatedProject();
|
||||
}, [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, projectFilePath(SubProject.logic, "decls/index.d.ts")]);
|
||||
}, () => [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, projectFilePath(SubProject.logic, "decls/index.d.ts")]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace ts.tscWatch {
|
||||
};
|
||||
const files = [file1, libFile];
|
||||
const environmentVariables = createMap<string>();
|
||||
environmentVariables.set("TSC_WATCHFILE", "DynamicPriorityPolling");
|
||||
environmentVariables.set("TSC_WATCHFILE", TestFSWithWatch.Tsc_WatchFile.DynamicPolling);
|
||||
const host = createWatchedSystem(files, { environmentVariables });
|
||||
const watch = createWatchOfFilesAndCompilerOptions([file1.path], host);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user