mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-16 15:45:27 -05:00
Add watchOptions to tsconfig and allow supplying them on command line as well (#35615)
* Create different watch options in compiler options * Thread through the new watch options * Actually use the options passed through for watch strategy * Support delay on updating child directory watches * Make watchOptions separate from compilerOptions * Support passing watch options from command line * Handle displaying of watchOptions
This commit is contained in:
@@ -83,6 +83,7 @@
|
||||
"unittests/config/projectReferences.ts",
|
||||
"unittests/config/showConfig.ts",
|
||||
"unittests/config/tsconfigParsing.ts",
|
||||
"unittests/config/tsconfigParsingWatchOptions.ts",
|
||||
"unittests/evaluation/asyncArrow.ts",
|
||||
"unittests/evaluation/asyncGenerator.ts",
|
||||
"unittests/evaluation/awaiter.ts",
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace ts {
|
||||
const parsedCompilerOptions = JSON.stringify(parsed.options);
|
||||
const expectedCompilerOptions = JSON.stringify(expectedParsedCommandLine.options);
|
||||
assert.equal(parsedCompilerOptions, expectedCompilerOptions);
|
||||
assert.deepEqual(parsed.watchOptions, expectedParsedCommandLine.watchOptions);
|
||||
|
||||
const parsedErrors = parsed.errors;
|
||||
const expectedErrors = expectedParsedCommandLine.errors;
|
||||
@@ -45,7 +46,7 @@ namespace ts {
|
||||
assertParseResult(["--declarations", "--allowTS"], {
|
||||
errors: [
|
||||
{
|
||||
messageText:"Unknown compiler option '--declarations'. Did you mean 'declaration'?",
|
||||
messageText: "Unknown compiler option '--declarations'. Did you mean 'declaration'?",
|
||||
category: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1.category,
|
||||
code: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1.code,
|
||||
file: undefined,
|
||||
@@ -412,6 +413,75 @@ namespace ts {
|
||||
options: { tsBuildInfoFile: "build.tsbuildinfo" }
|
||||
});
|
||||
});
|
||||
|
||||
describe("Watch options", () => {
|
||||
it("parse --watchFile", () => {
|
||||
assertParseResult(["--watchFile", "UseFsEvents", "0.ts"],
|
||||
{
|
||||
errors: [],
|
||||
fileNames: ["0.ts"],
|
||||
options: {},
|
||||
watchOptions: { watchFile: WatchFileKind.UseFsEvents }
|
||||
});
|
||||
});
|
||||
|
||||
it("parse --watchDirectory", () => {
|
||||
assertParseResult(["--watchDirectory", "FixedPollingInterval", "0.ts"],
|
||||
{
|
||||
errors: [],
|
||||
fileNames: ["0.ts"],
|
||||
options: {},
|
||||
watchOptions: { watchDirectory: WatchDirectoryKind.FixedPollingInterval }
|
||||
});
|
||||
});
|
||||
|
||||
it("parse --fallbackPolling", () => {
|
||||
assertParseResult(["--fallbackPolling", "PriorityInterval", "0.ts"],
|
||||
{
|
||||
errors: [],
|
||||
fileNames: ["0.ts"],
|
||||
options: {},
|
||||
watchOptions: { fallbackPolling: PollingWatchKind.PriorityInterval }
|
||||
});
|
||||
});
|
||||
|
||||
it("parse --synchronousWatchDirectory", () => {
|
||||
assertParseResult(["--synchronousWatchDirectory", "0.ts"],
|
||||
{
|
||||
errors: [],
|
||||
fileNames: ["0.ts"],
|
||||
options: {},
|
||||
watchOptions: { synchronousWatchDirectory: true }
|
||||
});
|
||||
});
|
||||
|
||||
it("errors on missing argument to --fallbackPolling", () => {
|
||||
assertParseResult(["0.ts", "--fallbackPolling"],
|
||||
{
|
||||
errors: [
|
||||
{
|
||||
messageText: "Watch option 'fallbackPolling' requires a value of type string.",
|
||||
category: Diagnostics.Watch_option_0_requires_a_value_of_type_1.category,
|
||||
code: Diagnostics.Watch_option_0_requires_a_value_of_type_1.code,
|
||||
file: undefined,
|
||||
start: undefined,
|
||||
length: undefined
|
||||
},
|
||||
{
|
||||
messageText: "Argument for '--fallbackPolling' option must be: 'fixedinterval', 'priorityinterval', 'dynamicpriority'.",
|
||||
category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category,
|
||||
code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
|
||||
file: undefined,
|
||||
start: undefined,
|
||||
length: undefined
|
||||
}
|
||||
],
|
||||
fileNames: ["0.ts"],
|
||||
options: {},
|
||||
watchOptions: { fallbackPolling: undefined }
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("unittests:: config:: commandLineParsing:: parseBuildOptions", () => {
|
||||
@@ -420,6 +490,7 @@ namespace ts {
|
||||
const parsedBuildOptions = JSON.stringify(parsed.buildOptions);
|
||||
const expectedBuildOptions = JSON.stringify(expectedParsedBuildCommand.buildOptions);
|
||||
assert.equal(parsedBuildOptions, expectedBuildOptions);
|
||||
assert.deepEqual(parsed.watchOptions, expectedParsedBuildCommand.watchOptions);
|
||||
|
||||
const parsedErrors = parsed.errors;
|
||||
const expectedErrors = expectedParsedBuildCommand.errors;
|
||||
@@ -442,7 +513,8 @@ namespace ts {
|
||||
{
|
||||
errors: [],
|
||||
projects: ["."],
|
||||
buildOptions: {}
|
||||
buildOptions: {},
|
||||
watchOptions: undefined
|
||||
});
|
||||
});
|
||||
|
||||
@@ -452,7 +524,8 @@ namespace ts {
|
||||
{
|
||||
errors: [],
|
||||
projects: ["tests"],
|
||||
buildOptions: { verbose: true, force: true }
|
||||
buildOptions: { verbose: true, force: true },
|
||||
watchOptions: undefined
|
||||
});
|
||||
});
|
||||
|
||||
@@ -469,7 +542,8 @@ namespace ts {
|
||||
length: undefined,
|
||||
}],
|
||||
projects: ["."],
|
||||
buildOptions: { verbose: true }
|
||||
buildOptions: { verbose: true },
|
||||
watchOptions: undefined
|
||||
});
|
||||
});
|
||||
|
||||
@@ -478,7 +552,7 @@ namespace ts {
|
||||
assertParseResult(["--listFilesOnly"],
|
||||
{
|
||||
errors: [{
|
||||
messageText:"Unknown build option '--listFilesOnly'.",
|
||||
messageText: "Unknown build option '--listFilesOnly'.",
|
||||
category: Diagnostics.Unknown_build_option_0.category,
|
||||
code: Diagnostics.Unknown_build_option_0.code,
|
||||
file: undefined,
|
||||
@@ -486,7 +560,8 @@ namespace ts {
|
||||
length: undefined,
|
||||
}],
|
||||
projects: ["."],
|
||||
buildOptions: {}
|
||||
buildOptions: {},
|
||||
watchOptions: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -496,7 +571,8 @@ namespace ts {
|
||||
{
|
||||
errors: [],
|
||||
projects: ["src", "tests"],
|
||||
buildOptions: { force: true, verbose: true }
|
||||
buildOptions: { force: true, verbose: true },
|
||||
watchOptions: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -506,7 +582,8 @@ namespace ts {
|
||||
{
|
||||
errors: [],
|
||||
projects: ["src", "tests"],
|
||||
buildOptions: { force: true, verbose: true }
|
||||
buildOptions: { force: true, verbose: true },
|
||||
watchOptions: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -516,7 +593,8 @@ namespace ts {
|
||||
{
|
||||
errors: [],
|
||||
projects: ["src", "tests"],
|
||||
buildOptions: { force: true, verbose: true }
|
||||
buildOptions: { force: true, verbose: true },
|
||||
watchOptions: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -526,7 +604,8 @@ namespace ts {
|
||||
{
|
||||
errors: [],
|
||||
projects: ["tests"],
|
||||
buildOptions: { incremental: true }
|
||||
buildOptions: { incremental: true },
|
||||
watchOptions: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -536,7 +615,8 @@ namespace ts {
|
||||
{
|
||||
errors: [],
|
||||
projects: ["src"],
|
||||
buildOptions: { locale: "en-us" }
|
||||
buildOptions: { locale: "en-us" },
|
||||
watchOptions: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -553,7 +633,8 @@ namespace ts {
|
||||
length: undefined
|
||||
}],
|
||||
projects: ["build.tsbuildinfo", "tests"],
|
||||
buildOptions: { }
|
||||
buildOptions: {},
|
||||
watchOptions: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -572,7 +653,8 @@ namespace ts {
|
||||
length: undefined,
|
||||
}],
|
||||
projects: ["."],
|
||||
buildOptions: { [flag1]: true, [flag2]: true }
|
||||
buildOptions: { [flag1]: true, [flag2]: true },
|
||||
watchOptions: undefined,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -582,7 +664,74 @@ namespace ts {
|
||||
verifyInvalidCombination("clean", "watch");
|
||||
verifyInvalidCombination("watch", "dry");
|
||||
});
|
||||
|
||||
describe("Watch options", () => {
|
||||
it("parse --watchFile", () => {
|
||||
assertParseResult(["--watchFile", "UseFsEvents", "--verbose"],
|
||||
{
|
||||
errors: [],
|
||||
projects: ["."],
|
||||
buildOptions: { verbose: true },
|
||||
watchOptions: { watchFile: WatchFileKind.UseFsEvents }
|
||||
});
|
||||
});
|
||||
|
||||
it("parse --watchDirectory", () => {
|
||||
assertParseResult(["--watchDirectory", "FixedPollingInterval", "--verbose"],
|
||||
{
|
||||
errors: [],
|
||||
projects: ["."],
|
||||
buildOptions: { verbose: true },
|
||||
watchOptions: { watchDirectory: WatchDirectoryKind.FixedPollingInterval }
|
||||
});
|
||||
});
|
||||
|
||||
it("parse --fallbackPolling", () => {
|
||||
assertParseResult(["--fallbackPolling", "PriorityInterval", "--verbose"],
|
||||
{
|
||||
errors: [],
|
||||
projects: ["."],
|
||||
buildOptions: { verbose: true },
|
||||
watchOptions: { fallbackPolling: PollingWatchKind.PriorityInterval }
|
||||
});
|
||||
});
|
||||
|
||||
it("parse --synchronousWatchDirectory", () => {
|
||||
assertParseResult(["--synchronousWatchDirectory", "--verbose"],
|
||||
{
|
||||
errors: [],
|
||||
projects: ["."],
|
||||
buildOptions: { verbose: true },
|
||||
watchOptions: { synchronousWatchDirectory: true }
|
||||
});
|
||||
});
|
||||
|
||||
it("errors on missing argument", () => {
|
||||
assertParseResult(["--verbose", "--fallbackPolling"],
|
||||
{
|
||||
errors: [
|
||||
{
|
||||
messageText: "Watch option 'fallbackPolling' requires a value of type string.",
|
||||
category: Diagnostics.Watch_option_0_requires_a_value_of_type_1.category,
|
||||
code: Diagnostics.Watch_option_0_requires_a_value_of_type_1.code,
|
||||
file: undefined,
|
||||
start: undefined,
|
||||
length: undefined
|
||||
},
|
||||
{
|
||||
messageText: "Argument for '--fallbackPolling' option must be: 'fixedinterval', 'priorityinterval', 'dynamicpriority'.",
|
||||
category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category,
|
||||
code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
|
||||
file: undefined,
|
||||
start: undefined,
|
||||
length: undefined
|
||||
}
|
||||
],
|
||||
projects: ["."],
|
||||
buildOptions: { verbose: true },
|
||||
watchOptions: { fallbackPolling: undefined }
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -102,16 +102,33 @@ namespace ts {
|
||||
]
|
||||
});
|
||||
|
||||
showTSConfigCorrectly("Show TSConfig with watch options", ["-p", "tsconfig.json"], {
|
||||
watchOptions: {
|
||||
watchFile: "DynamicPriorityPolling"
|
||||
},
|
||||
include: [
|
||||
"./src/**/*"
|
||||
]
|
||||
});
|
||||
|
||||
// Bulk validation of all option declarations
|
||||
for (const option of optionDeclarations) {
|
||||
if (option.name === "project") continue;
|
||||
let configObject: object | undefined;
|
||||
baselineOption(option, /*isCompilerOptions*/ true);
|
||||
}
|
||||
|
||||
for (const option of optionsForWatch) {
|
||||
baselineOption(option, /*isCompilerOptions*/ false);
|
||||
}
|
||||
|
||||
function baselineOption(option: CommandLineOption, isCompilerOptions: boolean) {
|
||||
if (option.name === "project") return;
|
||||
let args: string[];
|
||||
let optionValue: object | undefined;
|
||||
switch (option.type) {
|
||||
case "boolean": {
|
||||
if (option.isTSConfigOnly) {
|
||||
args = ["-p", "tsconfig.json"];
|
||||
configObject = { compilerOptions: { [option.name]: true } };
|
||||
optionValue = { [option.name]: true };
|
||||
}
|
||||
else {
|
||||
args = [`--${option.name}`];
|
||||
@@ -121,7 +138,7 @@ namespace ts {
|
||||
case "list": {
|
||||
if (option.isTSConfigOnly) {
|
||||
args = ["-p", "tsconfig.json"];
|
||||
configObject = { compilerOptions: { [option.name]: [] } };
|
||||
optionValue = { [option.name]: [] };
|
||||
}
|
||||
else {
|
||||
args = [`--${option.name}`];
|
||||
@@ -131,7 +148,7 @@ namespace ts {
|
||||
case "string": {
|
||||
if (option.isTSConfigOnly) {
|
||||
args = ["-p", "tsconfig.json"];
|
||||
configObject = { compilerOptions: { [option.name]: "someString" } };
|
||||
optionValue = { [option.name]: "someString" };
|
||||
}
|
||||
else {
|
||||
args = [`--${option.name}`, "someString"];
|
||||
@@ -141,7 +158,7 @@ namespace ts {
|
||||
case "number": {
|
||||
if (option.isTSConfigOnly) {
|
||||
args = ["-p", "tsconfig.json"];
|
||||
configObject = { compilerOptions: { [option.name]: 0 } };
|
||||
optionValue = { [option.name]: 0 };
|
||||
}
|
||||
else {
|
||||
args = [`--${option.name}`, "0"];
|
||||
@@ -150,7 +167,7 @@ namespace ts {
|
||||
}
|
||||
case "object": {
|
||||
args = ["-p", "tsconfig.json"];
|
||||
configObject = { compilerOptions: { [option.name]: {} } };
|
||||
optionValue = { [option.name]: {} };
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -159,7 +176,7 @@ namespace ts {
|
||||
const val = iterResult.value;
|
||||
if (option.isTSConfigOnly) {
|
||||
args = ["-p", "tsconfig.json"];
|
||||
configObject = { compilerOptions: { [option.name]: val } };
|
||||
optionValue = { [option.name]: val };
|
||||
}
|
||||
else {
|
||||
args = [`--${option.name}`, val];
|
||||
@@ -167,6 +184,9 @@ namespace ts {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const configObject = optionValue &&
|
||||
(isCompilerOptions ? { compilerOptions: optionValue } : { watchOptions: optionValue });
|
||||
showTSConfigCorrectly(`Shows tsconfig for single option/${option.name}`, args, configObject);
|
||||
}
|
||||
});
|
||||
|
||||
178
src/testRunner/unittests/config/tsconfigParsingWatchOptions.ts
Normal file
178
src/testRunner/unittests/config/tsconfigParsingWatchOptions.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
namespace ts {
|
||||
describe("unittests:: config:: tsconfigParsingWatchOptions:: parseConfigFileTextToJson", () => {
|
||||
function createParseConfigHost(additionalFiles?: vfs.FileSet) {
|
||||
return new fakes.ParseConfigHost(
|
||||
new vfs.FileSystem(
|
||||
/*ignoreCase*/ false,
|
||||
{
|
||||
cwd: "/",
|
||||
files: { "/": {}, "/a.ts": "", ...additionalFiles }
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
function getParsedCommandJson(json: object, additionalFiles?: vfs.FileSet, existingWatchOptions?: WatchOptions) {
|
||||
return parseJsonConfigFileContent(
|
||||
json,
|
||||
createParseConfigHost(additionalFiles),
|
||||
"/",
|
||||
/*existingOptions*/ undefined,
|
||||
"tsconfig.json",
|
||||
/*resolutionStack*/ undefined,
|
||||
/*extraFileExtensions*/ undefined,
|
||||
/*extendedConfigCache*/ undefined,
|
||||
existingWatchOptions,
|
||||
);
|
||||
}
|
||||
|
||||
function getParsedCommandJsonNode(json: object, additionalFiles?: vfs.FileSet, existingWatchOptions?: WatchOptions) {
|
||||
const parsed = parseJsonText("tsconfig.json", JSON.stringify(json));
|
||||
return parseJsonSourceFileConfigFileContent(
|
||||
parsed,
|
||||
createParseConfigHost(additionalFiles),
|
||||
"/",
|
||||
/*existingOptions*/ undefined,
|
||||
"tsconfig.json",
|
||||
/*resolutionStack*/ undefined,
|
||||
/*extraFileExtensions*/ undefined,
|
||||
/*extendedConfigCache*/ undefined,
|
||||
existingWatchOptions,
|
||||
);
|
||||
}
|
||||
|
||||
interface VerifyWatchOptions {
|
||||
json: object;
|
||||
expectedOptions: WatchOptions | undefined;
|
||||
additionalFiles?: vfs.FileSet;
|
||||
existingWatchOptions?: WatchOptions | undefined;
|
||||
}
|
||||
|
||||
function verifyWatchOptions(scenario: () => VerifyWatchOptions[]) {
|
||||
it("with json api", () => {
|
||||
for (const { json, expectedOptions, additionalFiles, existingWatchOptions } of scenario()) {
|
||||
const parsed = getParsedCommandJson(json, additionalFiles, existingWatchOptions);
|
||||
assert.deepEqual(parsed.watchOptions, expectedOptions);
|
||||
}
|
||||
});
|
||||
|
||||
it("with json source file api", () => {
|
||||
for (const { json, expectedOptions, additionalFiles, existingWatchOptions } of scenario()) {
|
||||
const parsed = getParsedCommandJsonNode(json, additionalFiles, existingWatchOptions);
|
||||
assert.deepEqual(parsed.watchOptions, expectedOptions);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe("no watchOptions specified option", () => {
|
||||
verifyWatchOptions(() => [{
|
||||
json: {},
|
||||
expectedOptions: undefined
|
||||
}]);
|
||||
});
|
||||
|
||||
describe("empty watchOptions specified option", () => {
|
||||
verifyWatchOptions(() => [{
|
||||
json: { watchOptions: {} },
|
||||
expectedOptions: undefined
|
||||
}]);
|
||||
});
|
||||
|
||||
describe("extending config file", () => {
|
||||
describe("when extending config file without watchOptions", () => {
|
||||
verifyWatchOptions(() => [
|
||||
{
|
||||
json: {
|
||||
extends: "./base.json",
|
||||
watchOptions: { watchFile: "UseFsEvents" }
|
||||
},
|
||||
expectedOptions: { watchFile: WatchFileKind.UseFsEvents },
|
||||
additionalFiles: { "/base.json": "{}" }
|
||||
},
|
||||
{
|
||||
json: { extends: "./base.json", },
|
||||
expectedOptions: undefined,
|
||||
additionalFiles: { "/base.json": "{}" }
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
describe("when extending config file with watchOptions", () => {
|
||||
verifyWatchOptions(() => [
|
||||
{
|
||||
json: {
|
||||
extends: "./base.json",
|
||||
watchOptions: {
|
||||
watchFile: "UseFsEvents",
|
||||
}
|
||||
},
|
||||
expectedOptions: {
|
||||
watchFile: WatchFileKind.UseFsEvents,
|
||||
watchDirectory: WatchDirectoryKind.FixedPollingInterval
|
||||
},
|
||||
additionalFiles: {
|
||||
"/base.json": JSON.stringify({
|
||||
watchOptions: {
|
||||
watchFile: "UseFsEventsOnParentDirectory",
|
||||
watchDirectory: "FixedPollingInterval"
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
json: {
|
||||
extends: "./base.json",
|
||||
},
|
||||
expectedOptions: {
|
||||
watchFile: WatchFileKind.UseFsEventsOnParentDirectory,
|
||||
watchDirectory: WatchDirectoryKind.FixedPollingInterval
|
||||
},
|
||||
additionalFiles: {
|
||||
"/base.json": JSON.stringify({
|
||||
watchOptions: {
|
||||
watchFile: "UseFsEventsOnParentDirectory",
|
||||
watchDirectory: "FixedPollingInterval"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("different options", () => {
|
||||
verifyWatchOptions(() => [
|
||||
{
|
||||
json: { watchOptions: { watchFile: "UseFsEvents" } },
|
||||
expectedOptions: { watchFile: WatchFileKind.UseFsEvents }
|
||||
},
|
||||
{
|
||||
json: { watchOptions: { watchDirectory: "UseFsEvents" } },
|
||||
expectedOptions: { watchDirectory: WatchDirectoryKind.UseFsEvents }
|
||||
},
|
||||
{
|
||||
json: { watchOptions: { fallbackPolling: "DynamicPriority" } },
|
||||
expectedOptions: { fallbackPolling: PollingWatchKind.DynamicPriority }
|
||||
},
|
||||
{
|
||||
json: { watchOptions: { synchronousWatchDirectory: true } },
|
||||
expectedOptions: { synchronousWatchDirectory: true }
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
describe("watch options extending passed in watch options", () => {
|
||||
verifyWatchOptions(() => [
|
||||
{
|
||||
json: { watchOptions: { watchFile: "UseFsEvents" } },
|
||||
expectedOptions: { watchFile: WatchFileKind.UseFsEvents, watchDirectory: WatchDirectoryKind.FixedPollingInterval },
|
||||
existingWatchOptions: { watchDirectory: WatchDirectoryKind.FixedPollingInterval }
|
||||
},
|
||||
{
|
||||
json: {},
|
||||
expectedOptions: { watchDirectory: WatchDirectoryKind.FixedPollingInterval },
|
||||
existingWatchOptions: { watchDirectory: WatchDirectoryKind.FixedPollingInterval }
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -928,13 +928,13 @@ namespace ts {
|
||||
}
|
||||
|
||||
function verifyProgramWithoutConfigFile(system: System, rootFiles: string[], options: CompilerOptions) {
|
||||
const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, system)).getCurrentProgram().getProgram();
|
||||
const program = createWatchProgram(createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, /*watchOptions*/ undefined, system)).getCurrentProgram().getProgram();
|
||||
verifyProgramIsUptoDate(program, duplicate(rootFiles), duplicate(options));
|
||||
}
|
||||
|
||||
function verifyProgramWithConfigFile(system: System, configFileName: string) {
|
||||
const program = createWatchProgram(createWatchCompilerHostOfConfigFile(configFileName, {}, system)).getCurrentProgram().getProgram();
|
||||
const { fileNames, options } = parseConfigFileWithSystem(configFileName, {}, system, notImplemented)!; // TODO: GH#18217
|
||||
const program = createWatchProgram(createWatchCompilerHostOfConfigFile(configFileName, {}, /*watchOptionsToExtend*/ undefined, system)).getCurrentProgram().getProgram();
|
||||
const { fileNames, options } = parseConfigFileWithSystem(configFileName, {}, /*watchOptionsToExtend*/ undefined, system, notImplemented)!; // TODO: GH#18217
|
||||
verifyProgramIsUptoDate(program, fileNames, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ namespace ts.tscWatch {
|
||||
describe("unittests:: tsc-watch:: console clearing", () => {
|
||||
const currentDirectoryLog = "Current directory: / CaseSensitiveFileNames: false\n";
|
||||
const fileWatcherAddedLog = [
|
||||
"FileWatcher:: Added:: WatchInfo: /f.ts 250 Source file\n",
|
||||
"FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 250 Source file\n"
|
||||
"FileWatcher:: Added:: WatchInfo: /f.ts 250 undefined Source file\n",
|
||||
"FileWatcher:: Added:: WatchInfo: /a/lib/lib.d.ts 250 undefined Source file\n"
|
||||
];
|
||||
|
||||
const file: File = {
|
||||
@@ -35,9 +35,9 @@ namespace ts.tscWatch {
|
||||
host.modifyFile(file.path, "//");
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
checkOutputErrorsIncremental(host, emptyArray, disableConsoleClear, hasLog ? [
|
||||
"FileWatcher:: Triggered with /f.ts 1:: WatchInfo: /f.ts 250 Source file\n",
|
||||
"FileWatcher:: Triggered with /f.ts 1:: WatchInfo: /f.ts 250 undefined Source file\n",
|
||||
"Scheduling update\n",
|
||||
"Elapsed:: 0ms FileWatcher:: Triggered with /f.ts 1:: WatchInfo: /f.ts 250 Source file\n"
|
||||
"Elapsed:: 0ms FileWatcher:: Triggered with /f.ts 1:: WatchInfo: /f.ts 250 undefined Source file\n"
|
||||
] : undefined, hasLog ? getProgramSynchronizingLog(options) : undefined);
|
||||
}
|
||||
|
||||
@@ -86,8 +86,8 @@ namespace ts.tscWatch {
|
||||
const host = createWatchedSystem(files);
|
||||
const reportDiagnostic = createDiagnosticReporter(host);
|
||||
const optionsToExtend: CompilerOptions = {};
|
||||
const configParseResult = parseConfigFileWithSystem(configFile.path, optionsToExtend, host, reportDiagnostic)!;
|
||||
const watchCompilerHost = createWatchCompilerHostOfConfigFile(configParseResult.options.configFilePath!, optionsToExtend, host, /*createProgram*/ undefined, reportDiagnostic, createWatchStatusReporter(host));
|
||||
const configParseResult = parseConfigFileWithSystem(configFile.path, optionsToExtend, /*watchOptionsToExtend*/ undefined, host, reportDiagnostic)!;
|
||||
const watchCompilerHost = createWatchCompilerHostOfConfigFile(configParseResult.options.configFilePath!, optionsToExtend, /*watchOptionsToExtend*/ undefined, host, /*createProgram*/ undefined, reportDiagnostic, createWatchStatusReporter(host));
|
||||
watchCompilerHost.configFileParsingResult = configParseResult;
|
||||
createWatchProgram(watchCompilerHost);
|
||||
verifyCompilation(host, compilerOptions);
|
||||
|
||||
@@ -37,8 +37,8 @@ namespace ts.tscWatch {
|
||||
close(): void;
|
||||
}
|
||||
|
||||
export function createWatchOfConfigFile(configFileName: string, host: WatchedSystem, optionsToExtend?: CompilerOptions, maxNumberOfFilesToIterateForInvalidation?: number) {
|
||||
const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, optionsToExtend || {}, host);
|
||||
export function createWatchOfConfigFile(configFileName: string, host: WatchedSystem, optionsToExtend?: CompilerOptions, watchOptionsToExtend?: WatchOptions, maxNumberOfFilesToIterateForInvalidation?: number) {
|
||||
const compilerHost = createWatchCompilerHostOfConfigFile(configFileName, optionsToExtend || {}, watchOptionsToExtend, host);
|
||||
compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;
|
||||
const watch = createWatchProgram(compilerHost);
|
||||
const result = (() => watch.getCurrentProgram().getProgram()) as Watch;
|
||||
@@ -47,8 +47,8 @@ namespace ts.tscWatch {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], host: WatchedSystem, options: CompilerOptions = {}, maxNumberOfFilesToIterateForInvalidation?: number) {
|
||||
const compilerHost = createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, host);
|
||||
export function createWatchOfFilesAndCompilerOptions(rootFiles: string[], host: WatchedSystem, options: CompilerOptions = {}, watchOptions?: WatchOptions, maxNumberOfFilesToIterateForInvalidation?: number) {
|
||||
const compilerHost = createWatchCompilerHostOfFilesAndCompilerOptions(rootFiles, options, watchOptions, host);
|
||||
compilerHost.maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation;
|
||||
const watch = createWatchProgram(compilerHost);
|
||||
return () => watch.getCurrentProgram().getProgram();
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace ts.tscWatch {
|
||||
|
||||
function incrementalBuild(configFile: string, host: WatchedSystem, optionsToExtend?: CompilerOptions) {
|
||||
const reportDiagnostic = createDiagnosticReporter(host);
|
||||
const config = parseConfigFileWithSystem(configFile, optionsToExtend || {}, host, reportDiagnostic);
|
||||
const config = parseConfigFileWithSystem(configFile, optionsToExtend || {}, /*watchOptionsToExtend*/ undefined, host, reportDiagnostic);
|
||||
if (config) {
|
||||
performIncrementalCompilation({
|
||||
rootNames: config.fileNames,
|
||||
@@ -547,7 +547,7 @@ namespace ts.tscWatch {
|
||||
const system = createWatchedSystem([libFile, file1, fileModified, config], { currentDirectory: project });
|
||||
incrementalBuild("tsconfig.json", system);
|
||||
|
||||
const command = parseConfigFileWithSystem("tsconfig.json", {}, system, noop)!;
|
||||
const command = parseConfigFileWithSystem("tsconfig.json", {}, /*watchOptionsToExtend*/ undefined, system, noop)!;
|
||||
const builderProgram = createIncrementalProgram({
|
||||
rootNames: command.fileNames,
|
||||
options: command.options,
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace ts.tscWatch {
|
||||
};
|
||||
|
||||
const host = createWatchedSystem([configFile, libFile, file1, file2, file3]);
|
||||
const watch = createWatchProgram(createWatchCompilerHostOfConfigFile(configFile.path, {}, host, /*createProgram*/ undefined, notImplemented));
|
||||
const watch = createWatchProgram(createWatchCompilerHostOfConfigFile(configFile.path, {}, /*watchOptionsToExtend*/ undefined, host, /*createProgram*/ undefined, notImplemented));
|
||||
|
||||
checkProgramActualFiles(watch.getCurrentProgram().getProgram(), [file1.path, libFile.path, file2.path]);
|
||||
checkProgramRootFiles(watch.getCurrentProgram().getProgram(), [file1.path, file2.path]);
|
||||
@@ -931,7 +931,7 @@ namespace ts.tscWatch {
|
||||
content: generateTSConfig(options, emptyArray, "\n")
|
||||
};
|
||||
const host = createWatchedSystem([file1, file2, libFile, tsconfig], { currentDirectory: projectRoot });
|
||||
const watch = createWatchOfConfigFile(tsconfig.path, host, /*optionsToExtend*/ undefined, /*maxNumberOfFilesToIterateForInvalidation*/1);
|
||||
const watch = createWatchOfConfigFile(tsconfig.path, host, /*optionsToExtend*/ undefined, /*watchOptionsToExtend*/ undefined, /*maxNumberOfFilesToIterateForInvalidation*/1);
|
||||
checkProgramActualFiles(watch(), [file1.path, file2.path, libFile.path]);
|
||||
|
||||
outputFiles.forEach(f => host.fileExists(f));
|
||||
|
||||
@@ -379,7 +379,7 @@ declare module "fs" {
|
||||
const expectedFiles = files.map(f => f.path);
|
||||
it("when watching node_modules in inferred project for failed lookup", () => {
|
||||
const host = createWatchedSystem(files);
|
||||
const watch = createWatchOfFilesAndCompilerOptions([file1.path], host, {}, /*maxNumberOfFilesToIterateForInvalidation*/ 1);
|
||||
const watch = createWatchOfFilesAndCompilerOptions([file1.path], host, {}, /*watchOptions*/ undefined, /*maxNumberOfFilesToIterateForInvalidation*/ 1);
|
||||
checkProgramActualFiles(watch(), expectedFiles);
|
||||
host.checkTimeoutQueueLength(0);
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace ts.tscWatch {
|
||||
it("verify that module resolution with json extension works when returned without extension", () => {
|
||||
const files = [libFile, mainFile, config, settingsJson];
|
||||
const host = createWatchedSystem(files, { currentDirectory: projectRoot });
|
||||
const compilerHost = createWatchCompilerHostOfConfigFile(config.path, {}, host);
|
||||
const compilerHost = createWatchCompilerHostOfConfigFile(config.path, {}, /*watchOptionsToExtend*/ undefined, host);
|
||||
const parsedCommandResult = parseJsonConfigFileContent(configFileJson, host, config.path);
|
||||
compilerHost.resolveModuleNames = (moduleNames, containingFile) => moduleNames.map(m => {
|
||||
const result = resolveModuleName(m, containingFile, parsedCommandResult.options, compilerHost);
|
||||
@@ -58,7 +58,7 @@ namespace ts.tscWatch {
|
||||
const reportWatchStatus: WatchStatusReporter = (_, __, ___, errorCount) => {
|
||||
watchedErrorCount = errorCount;
|
||||
};
|
||||
const compilerHost = createWatchCompilerHostOfConfigFile(config.path, {}, host, /*createProgram*/ undefined, /*reportDiagnostic*/ undefined, reportWatchStatus);
|
||||
const compilerHost = createWatchCompilerHostOfConfigFile(config.path, {}, /*watchOptionsToExtend*/ undefined, host, /*createProgram*/ undefined, /*reportDiagnostic*/ undefined, reportWatchStatus);
|
||||
createWatchProgram(compilerHost);
|
||||
assert.equal(watchedErrorCount, 2, "The error count was expected to be 2 for the file change");
|
||||
});
|
||||
|
||||
@@ -68,7 +68,11 @@ namespace ts.tscWatch {
|
||||
const projectSrcFolder = `${projectFolder}/src`;
|
||||
const configFile: File = {
|
||||
path: `${projectFolder}/tsconfig.json`,
|
||||
content: "{}"
|
||||
content: JSON.stringify({
|
||||
watchOptions: {
|
||||
synchronousWatchDirectory: true
|
||||
}
|
||||
})
|
||||
};
|
||||
const file: File = {
|
||||
path: `${projectSrcFolder}/file1.ts`,
|
||||
@@ -173,6 +177,277 @@ namespace ts.tscWatch {
|
||||
checkWatchedDirectories(host, [cwd, `${cwd}/node_modules`, `${cwd}/node_modules/@types`, `${cwd}/node_modules/reala`, `${cwd}/node_modules/realb`,
|
||||
`${cwd}/node_modules/reala/node_modules`, `${cwd}/node_modules/realb/node_modules`, `${cwd}/src`], /*recursive*/ false);
|
||||
});
|
||||
|
||||
it("with non synchronous watch directory", () => {
|
||||
const configFile: File = {
|
||||
path: `${projectRoot}/tsconfig.json`,
|
||||
content: "{}"
|
||||
};
|
||||
const file1: File = {
|
||||
path: `${projectRoot}/src/file1.ts`,
|
||||
content: `import { x } from "file2";`
|
||||
};
|
||||
const file2: File = {
|
||||
path: `${projectRoot}/node_modules/file2/index.d.ts`,
|
||||
content: `export const x = 10;`
|
||||
};
|
||||
const files = [libFile, file1, file2, configFile];
|
||||
const host = createWatchedSystem(files, { runWithoutRecursiveWatches: true });
|
||||
const watch = createWatchOfConfigFile(configFile.path, host);
|
||||
checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path));
|
||||
checkOutputErrorsInitial(host, emptyArray);
|
||||
const watchedDirectories = [`${projectRoot}`, `${projectRoot}/src`, `${projectRoot}/node_modules`, `${projectRoot}/node_modules/file2`, `${projectRoot}/node_modules/@types`];
|
||||
checkWatchesWithFile2();
|
||||
host.checkTimeoutQueueLengthAndRun(1); // To update directory callbacks for file1.js output
|
||||
host.checkTimeoutQueueLengthAndRun(1); // Update program again
|
||||
host.checkTimeoutQueueLength(0);
|
||||
checkOutputErrorsIncremental(host, emptyArray);
|
||||
checkWatchesWithFile2();
|
||||
|
||||
// Remove directory node_modules
|
||||
host.deleteFolder(`${projectRoot}/node_modules`, /*recursive*/ true);
|
||||
host.checkTimeoutQueueLength(2); // 1. For updating program and 2. for updating child watches
|
||||
host.runQueuedTimeoutCallbacks(host.getNextTimeoutId() - 2); // Update program
|
||||
checkOutputErrorsIncremental(host, [
|
||||
getDiagnosticModuleNotFoundOfFile(watch(), file1, "file2")
|
||||
]);
|
||||
checkWatchesWithoutFile2();
|
||||
|
||||
host.checkTimeoutQueueLengthAndRun(1); // To update directory watchers
|
||||
host.checkTimeoutQueueLengthAndRun(1); // To Update program
|
||||
host.checkTimeoutQueueLength(0);
|
||||
checkWatchesWithoutFile2();
|
||||
checkOutputErrorsIncremental(host, [
|
||||
getDiagnosticModuleNotFoundOfFile(watch(), file1, "file2")
|
||||
]);
|
||||
|
||||
// npm install
|
||||
host.createDirectory(`${projectRoot}/node_modules`);
|
||||
host.checkTimeoutQueueLength(1); // To update folder structure
|
||||
assert.deepEqual(host.getOutput(), emptyArray);
|
||||
checkWatchesWithoutFile2();
|
||||
host.createDirectory(`${projectRoot}/node_modules/file2`);
|
||||
host.checkTimeoutQueueLength(1); // To update folder structure
|
||||
assert.deepEqual(host.getOutput(), emptyArray);
|
||||
checkWatchesWithoutFile2();
|
||||
host.writeFile(file2.path, file2.content);
|
||||
host.checkTimeoutQueueLength(1); // To update folder structure
|
||||
assert.deepEqual(host.getOutput(), emptyArray);
|
||||
checkWatchesWithoutFile2();
|
||||
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
host.checkTimeoutQueueLength(1); // To Update the program
|
||||
assert.deepEqual(host.getOutput(), emptyArray);
|
||||
checkWatchedFiles(files.filter(f => f !== file2)); // Files like without file2
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
|
||||
checkNonRecursiveWatchedDirectories(watchedDirectories); // Directories like with file2
|
||||
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
host.checkTimeoutQueueLength(0);
|
||||
checkOutputErrorsIncremental(host, emptyArray);
|
||||
checkWatchesWithFile2();
|
||||
|
||||
function checkWatchesWithFile2() {
|
||||
checkWatchedFiles(files);
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
|
||||
checkNonRecursiveWatchedDirectories(watchedDirectories);
|
||||
}
|
||||
|
||||
function checkWatchesWithoutFile2() {
|
||||
checkWatchedFiles(files.filter(f => f !== file2));
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
|
||||
checkNonRecursiveWatchedDirectories(watchedDirectories.filter(f => f !== `${projectRoot}/node_modules/file2`));
|
||||
}
|
||||
|
||||
function checkWatchedFiles(files: readonly File[]) {
|
||||
checkWatchedFilesDetailed(
|
||||
host,
|
||||
files.map(f => f.path.toLowerCase()),
|
||||
1,
|
||||
arrayToMap(
|
||||
files,
|
||||
f => f.path.toLowerCase(),
|
||||
() => [PollingInterval.Low]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function checkNonRecursiveWatchedDirectories(directories: readonly string[]) {
|
||||
checkWatchedDirectoriesDetailed(
|
||||
host,
|
||||
directories,
|
||||
1,
|
||||
/*recursive*/ false,
|
||||
arrayToMap(
|
||||
directories,
|
||||
identity,
|
||||
() => [{
|
||||
fallbackPollingInterval: PollingInterval.Medium,
|
||||
fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
|
||||
}]
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("handles watch compiler options", () => {
|
||||
it("with watchFile option", () => {
|
||||
const configFile: File = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: JSON.stringify({
|
||||
watchOptions: {
|
||||
watchFile: "UseFsEvents"
|
||||
}
|
||||
})
|
||||
};
|
||||
const files = [libFile, commonFile1, commonFile2, configFile];
|
||||
const host = createWatchedSystem(files);
|
||||
const watch = createWatchOfConfigFile(configFile.path, host, { extendedDiagnostics: true });
|
||||
checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path));
|
||||
|
||||
// Instead of polling watch (= watchedFiles), uses fsWatch
|
||||
checkWatchedFiles(host, emptyArray);
|
||||
checkWatchedDirectoriesDetailed(
|
||||
host,
|
||||
files.map(f => f.path.toLowerCase()),
|
||||
1,
|
||||
/*recursive*/ false,
|
||||
arrayToMap(
|
||||
files,
|
||||
f => f.path.toLowerCase(),
|
||||
f => [{
|
||||
fallbackPollingInterval: f === configFile ? PollingInterval.High : PollingInterval.Low,
|
||||
fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
|
||||
}]
|
||||
)
|
||||
);
|
||||
checkWatchedDirectoriesDetailed(
|
||||
host,
|
||||
["/a/b", "/a/b/node_modules/@types"],
|
||||
1,
|
||||
/*recursive*/ true,
|
||||
arrayToMap(
|
||||
["/a/b", "/a/b/node_modules/@types"],
|
||||
identity,
|
||||
() => [{
|
||||
fallbackPollingInterval: PollingInterval.Medium,
|
||||
fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
|
||||
}]
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("with watchDirectory option", () => {
|
||||
const configFile: File = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: JSON.stringify({
|
||||
watchOptions: {
|
||||
watchDirectory: "UseFsEvents"
|
||||
}
|
||||
})
|
||||
};
|
||||
const files = [libFile, commonFile1, commonFile2, configFile];
|
||||
const host = createWatchedSystem(files, { runWithoutRecursiveWatches: true });
|
||||
const watch = createWatchOfConfigFile(configFile.path, host, { extendedDiagnostics: true });
|
||||
checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path));
|
||||
|
||||
checkWatchedFilesDetailed(
|
||||
host,
|
||||
files.map(f => f.path.toLowerCase()),
|
||||
1,
|
||||
arrayToMap(
|
||||
files,
|
||||
f => f.path.toLowerCase(),
|
||||
() => [PollingInterval.Low]
|
||||
)
|
||||
);
|
||||
checkWatchedDirectoriesDetailed(
|
||||
host,
|
||||
["/a/b", "/a/b/node_modules/@types"],
|
||||
1,
|
||||
/*recursive*/ false,
|
||||
arrayToMap(
|
||||
["/a/b", "/a/b/node_modules/@types"],
|
||||
identity,
|
||||
() => [{
|
||||
fallbackPollingInterval: PollingInterval.Medium,
|
||||
fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
|
||||
}]
|
||||
)
|
||||
);
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
|
||||
});
|
||||
|
||||
it("with fallbackPolling option", () => {
|
||||
const configFile: File = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: JSON.stringify({
|
||||
watchOptions: {
|
||||
fallbackPolling: "PriorityInterval"
|
||||
}
|
||||
})
|
||||
};
|
||||
const files = [libFile, commonFile1, commonFile2, configFile];
|
||||
const host = createWatchedSystem(files, { runWithoutRecursiveWatches: true, runWithFallbackPolling: true });
|
||||
const watch = createWatchOfConfigFile(configFile.path, host, { extendedDiagnostics: true });
|
||||
checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path));
|
||||
const filePaths = files.map(f => f.path.toLowerCase());
|
||||
checkWatchedFilesDetailed(
|
||||
host,
|
||||
filePaths.concat(["/a/b", "/a/b/node_modules/@types"]),
|
||||
1,
|
||||
arrayToMap(
|
||||
filePaths.concat(["/a/b", "/a/b/node_modules/@types"]),
|
||||
identity,
|
||||
f => [contains(filePaths, f) ? PollingInterval.Low : PollingInterval.Medium]
|
||||
)
|
||||
);
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
|
||||
});
|
||||
|
||||
it("with watchFile as watch options to extend", () => {
|
||||
const configFile: File = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: "{}"
|
||||
};
|
||||
const files = [libFile, commonFile1, commonFile2, configFile];
|
||||
const host = createWatchedSystem(files);
|
||||
const watch = createWatchOfConfigFile(configFile.path, host, { extendedDiagnostics: true }, { watchFile: WatchFileKind.UseFsEvents });
|
||||
checkProgramActualFiles(watch(), mapDefined(files, f => f === configFile ? undefined : f.path));
|
||||
|
||||
// Instead of polling watch (= watchedFiles), uses fsWatch
|
||||
checkWatchedFiles(host, emptyArray);
|
||||
checkWatchedDirectoriesDetailed(
|
||||
host,
|
||||
files.map(f => f.path.toLowerCase()),
|
||||
1,
|
||||
/*recursive*/ false,
|
||||
arrayToMap(
|
||||
files,
|
||||
f => f.path.toLowerCase(),
|
||||
f => [{
|
||||
fallbackPollingInterval: f === configFile ? PollingInterval.High : PollingInterval.Low,
|
||||
fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
|
||||
}]
|
||||
)
|
||||
);
|
||||
checkWatchedDirectoriesDetailed(
|
||||
host,
|
||||
["/a/b", "/a/b/node_modules/@types"],
|
||||
1,
|
||||
/*recursive*/ true,
|
||||
arrayToMap(
|
||||
["/a/b", "/a/b/node_modules/@types"],
|
||||
identity,
|
||||
() => [{
|
||||
fallbackPollingInterval: PollingInterval.Medium,
|
||||
fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
|
||||
}]
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@ namespace ts.projectSystem {
|
||||
readDirectory = "readDirectory"
|
||||
}
|
||||
type CalledMaps = CalledMapsWithSingleArg | CalledMapsWithFiveArgs;
|
||||
type CalledWithFiveArgs = [readonly string[], readonly string[], readonly string[], number];
|
||||
function createCallsTrackingHost(host: TestServerHost) {
|
||||
const calledMaps: Record<CalledMapsWithSingleArg, MultiMap<true>> & Record<CalledMapsWithFiveArgs, MultiMap<[readonly string[], readonly string[], readonly string[], number]>> = {
|
||||
const calledMaps: Record<CalledMapsWithSingleArg, MultiMap<true>> & Record<CalledMapsWithFiveArgs, MultiMap<CalledWithFiveArgs>> = {
|
||||
fileExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.fileExists),
|
||||
directoryExists: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.directoryExists),
|
||||
getDirectories: setCallsTrackingWithSingleArgFn(CalledMapsWithSingleArg.getDirectories),
|
||||
@@ -65,11 +66,11 @@ namespace ts.projectSystem {
|
||||
}
|
||||
|
||||
function verifyCalledOnEachEntry(callback: CalledMaps, expectedKeys: Map<number>) {
|
||||
TestFSWithWatch.checkMultiMapKeyCount(callback, calledMaps[callback], expectedKeys);
|
||||
TestFSWithWatch.checkMap<true | CalledWithFiveArgs>(callback, calledMaps[callback], expectedKeys);
|
||||
}
|
||||
|
||||
function verifyCalledOnEachEntryNTimes(callback: CalledMaps, expectedKeys: readonly string[], nTimes: number) {
|
||||
TestFSWithWatch.checkMultiMapKeyCount(callback, calledMaps[callback], expectedKeys, nTimes);
|
||||
TestFSWithWatch.checkMap<true | CalledWithFiveArgs>(callback, calledMaps[callback], expectedKeys, nTimes);
|
||||
}
|
||||
|
||||
function verifyNoHostCalls() {
|
||||
@@ -689,10 +690,10 @@ namespace ts.projectSystem {
|
||||
};
|
||||
files.push(debugTypesFile);
|
||||
// Do not invoke recursive directory watcher for anything other than node_module/@types
|
||||
const invoker = host.invokeWatchedDirectoriesRecursiveCallback;
|
||||
host.invokeWatchedDirectoriesRecursiveCallback = (fullPath, relativePath) => {
|
||||
const invoker = host.invokeFsWatchesRecursiveCallbacks;
|
||||
host.invokeFsWatchesRecursiveCallbacks = (fullPath, eventName, entryFullPath) => {
|
||||
if (fullPath.endsWith("@types")) {
|
||||
invoker.call(host, fullPath, relativePath);
|
||||
invoker.call(host, fullPath, eventName, entryFullPath);
|
||||
}
|
||||
};
|
||||
host.reloadFS(files);
|
||||
|
||||
@@ -6,7 +6,11 @@ namespace ts.projectSystem {
|
||||
const projectSrcFolder = `${projectFolder}/src`;
|
||||
const configFile: File = {
|
||||
path: `${projectFolder}/tsconfig.json`,
|
||||
content: "{}"
|
||||
content: JSON.stringify({
|
||||
watchOptions: {
|
||||
synchronousWatchDirectory: true
|
||||
}
|
||||
})
|
||||
};
|
||||
const index: File = {
|
||||
path: `${projectSrcFolder}/index.ts`,
|
||||
@@ -246,4 +250,298 @@ namespace ts.projectSystem {
|
||||
verifyFilePathStyle("//vda1cs4850/c$/users/username/myprojects/project/x.js");
|
||||
});
|
||||
});
|
||||
|
||||
describe("unittests:: tsserver:: watchEnvironment:: handles watch compiler options", () => {
|
||||
it("with watchFile option as host configuration", () => {
|
||||
const configFile: File = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: "{}"
|
||||
};
|
||||
const files = [libFile, commonFile2, configFile];
|
||||
const host = createServerHost(files.concat(commonFile1));
|
||||
const session = createSession(host);
|
||||
session.executeCommandSeq<protocol.ConfigureRequest>({
|
||||
command: protocol.CommandTypes.Configure,
|
||||
arguments: {
|
||||
watchOptions: {
|
||||
watchFile: protocol.WatchFileKind.UseFsEvents
|
||||
}
|
||||
}
|
||||
});
|
||||
const service = session.getProjectService();
|
||||
openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session);
|
||||
checkProjectActualFiles(
|
||||
service.configuredProjects.get(configFile.path)!,
|
||||
files.map(f => f.path).concat(commonFile1.path)
|
||||
);
|
||||
|
||||
// Instead of polling watch (= watchedFiles), uses fsWatch
|
||||
checkWatchedFiles(host, emptyArray);
|
||||
checkWatchedDirectoriesDetailed(
|
||||
host,
|
||||
files.map(f => f.path.toLowerCase()),
|
||||
1,
|
||||
/*recursive*/ false,
|
||||
arrayToMap(
|
||||
files,
|
||||
f => f.path.toLowerCase(),
|
||||
f => [{
|
||||
fallbackPollingInterval: f === configFile ? PollingInterval.High : PollingInterval.Medium,
|
||||
fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
|
||||
}]
|
||||
)
|
||||
);
|
||||
checkWatchedDirectoriesDetailed(
|
||||
host,
|
||||
["/a/b", "/a/b/node_modules/@types"],
|
||||
1,
|
||||
/*recursive*/ true,
|
||||
arrayToMap(
|
||||
["/a/b", "/a/b/node_modules/@types"],
|
||||
identity,
|
||||
() => [{
|
||||
fallbackPollingInterval: PollingInterval.Medium,
|
||||
fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
|
||||
}]
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("with watchDirectory option as host configuration", () => {
|
||||
const configFile: File = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: "{}"
|
||||
};
|
||||
const files = [libFile, commonFile2, configFile];
|
||||
const host = createServerHost(files.concat(commonFile1), { runWithoutRecursiveWatches: true });
|
||||
const session = createSession(host);
|
||||
session.executeCommandSeq<protocol.ConfigureRequest>({
|
||||
command: protocol.CommandTypes.Configure,
|
||||
arguments: {
|
||||
watchOptions: {
|
||||
watchDirectory: protocol.WatchDirectoryKind.UseFsEvents
|
||||
}
|
||||
}
|
||||
});
|
||||
const service = session.getProjectService();
|
||||
openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session);
|
||||
checkProjectActualFiles(
|
||||
service.configuredProjects.get(configFile.path)!,
|
||||
files.map(f => f.path).concat(commonFile1.path)
|
||||
);
|
||||
|
||||
checkWatchedFilesDetailed(
|
||||
host,
|
||||
files.map(f => f.path.toLowerCase()),
|
||||
1,
|
||||
arrayToMap(
|
||||
files,
|
||||
f => f.path.toLowerCase(),
|
||||
() => [PollingInterval.Low]
|
||||
)
|
||||
);
|
||||
checkWatchedDirectoriesDetailed(
|
||||
host,
|
||||
["/a/b", "/a/b/node_modules/@types"],
|
||||
1,
|
||||
/*recursive*/ false,
|
||||
arrayToMap(
|
||||
["/a/b", "/a/b/node_modules/@types"],
|
||||
identity,
|
||||
() => [{
|
||||
fallbackPollingInterval: PollingInterval.Medium,
|
||||
fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
|
||||
}]
|
||||
)
|
||||
);
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
|
||||
});
|
||||
|
||||
it("with fallbackPolling option as host configuration", () => {
|
||||
const configFile: File = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: "{}"
|
||||
};
|
||||
const files = [libFile, commonFile2, configFile];
|
||||
const host = createServerHost(files.concat(commonFile1), { runWithoutRecursiveWatches: true, runWithFallbackPolling: true });
|
||||
const session = createSession(host);
|
||||
session.executeCommandSeq<protocol.ConfigureRequest>({
|
||||
command: protocol.CommandTypes.Configure,
|
||||
arguments: {
|
||||
watchOptions: {
|
||||
fallbackPolling: protocol.PollingWatchKind.PriorityInterval
|
||||
}
|
||||
}
|
||||
});
|
||||
const service = session.getProjectService();
|
||||
openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session);
|
||||
checkProjectActualFiles(
|
||||
service.configuredProjects.get(configFile.path)!,
|
||||
files.map(f => f.path).concat(commonFile1.path)
|
||||
);
|
||||
|
||||
const filePaths = files.map(f => f.path.toLowerCase());
|
||||
checkWatchedFilesDetailed(
|
||||
host,
|
||||
filePaths.concat(["/a/b", "/a/b/node_modules/@types"]),
|
||||
1,
|
||||
arrayToMap(
|
||||
filePaths.concat(["/a/b", "/a/b/node_modules/@types"]),
|
||||
identity,
|
||||
f => [contains(filePaths, f) ? PollingInterval.Low : PollingInterval.Medium]
|
||||
)
|
||||
);
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
|
||||
});
|
||||
|
||||
it("with watchFile option in configFile", () => {
|
||||
const configFile: File = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: JSON.stringify({
|
||||
watchOptions: {
|
||||
watchFile: "UseFsEvents"
|
||||
}
|
||||
})
|
||||
};
|
||||
const files = [libFile, commonFile2, configFile];
|
||||
const host = createServerHost(files.concat(commonFile1));
|
||||
const session = createSession(host);
|
||||
const service = session.getProjectService();
|
||||
openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session);
|
||||
checkProjectActualFiles(
|
||||
service.configuredProjects.get(configFile.path)!,
|
||||
files.map(f => f.path).concat(commonFile1.path)
|
||||
);
|
||||
|
||||
// The closed script infos are watched using host settings
|
||||
checkWatchedFilesDetailed(
|
||||
host,
|
||||
[libFile, commonFile2].map(f => f.path.toLowerCase()),
|
||||
1,
|
||||
arrayToMap(
|
||||
[libFile, commonFile2],
|
||||
f => f.path.toLowerCase(),
|
||||
() => [PollingInterval.Low]
|
||||
)
|
||||
);
|
||||
// Config file with the setting with fsWatch
|
||||
checkWatchedDirectoriesDetailed(
|
||||
host,
|
||||
[configFile.path.toLowerCase()],
|
||||
1,
|
||||
/*recursive*/ false,
|
||||
arrayToMap(
|
||||
[configFile.path.toLowerCase()],
|
||||
identity,
|
||||
() => [{
|
||||
fallbackPollingInterval: PollingInterval.High,
|
||||
fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
|
||||
}]
|
||||
)
|
||||
);
|
||||
checkWatchedDirectoriesDetailed(
|
||||
host,
|
||||
["/a/b", "/a/b/node_modules/@types"],
|
||||
1,
|
||||
/*recursive*/ true,
|
||||
arrayToMap(
|
||||
["/a/b", "/a/b/node_modules/@types"],
|
||||
identity,
|
||||
() => [{
|
||||
fallbackPollingInterval: PollingInterval.Medium,
|
||||
fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
|
||||
}]
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("with watchDirectory option in configFile", () => {
|
||||
const configFile: File = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: JSON.stringify({
|
||||
watchOptions: {
|
||||
watchDirectory: "UseFsEvents"
|
||||
}
|
||||
})
|
||||
};
|
||||
const files = [libFile, commonFile2, configFile];
|
||||
const host = createServerHost(files.concat(commonFile1), { runWithoutRecursiveWatches: true });
|
||||
const session = createSession(host);
|
||||
const service = session.getProjectService();
|
||||
openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session);
|
||||
checkProjectActualFiles(
|
||||
service.configuredProjects.get(configFile.path)!,
|
||||
files.map(f => f.path).concat(commonFile1.path)
|
||||
);
|
||||
|
||||
checkWatchedFilesDetailed(
|
||||
host,
|
||||
files.map(f => f.path.toLowerCase()),
|
||||
1,
|
||||
arrayToMap(
|
||||
files,
|
||||
f => f.path.toLowerCase(),
|
||||
() => [PollingInterval.Low]
|
||||
)
|
||||
);
|
||||
checkWatchedDirectoriesDetailed(
|
||||
host,
|
||||
["/a/b", "/a/b/node_modules/@types"],
|
||||
1,
|
||||
/*recursive*/ false,
|
||||
arrayToMap(
|
||||
["/a/b", "/a/b/node_modules/@types"],
|
||||
identity,
|
||||
() => [{
|
||||
fallbackPollingInterval: PollingInterval.Medium,
|
||||
fallbackOptions: { watchFile: WatchFileKind.PriorityPollingInterval }
|
||||
}]
|
||||
)
|
||||
);
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
|
||||
});
|
||||
|
||||
it("with fallbackPolling option in configFile", () => {
|
||||
const configFile: File = {
|
||||
path: "/a/b/tsconfig.json",
|
||||
content: JSON.stringify({
|
||||
watchOptions: {
|
||||
fallbackPolling: "PriorityInterval"
|
||||
}
|
||||
})
|
||||
};
|
||||
const files = [libFile, commonFile2, configFile];
|
||||
const host = createServerHost(files.concat(commonFile1), { runWithoutRecursiveWatches: true, runWithFallbackPolling: true });
|
||||
const session = createSession(host);
|
||||
session.executeCommandSeq<protocol.ConfigureRequest>({
|
||||
command: protocol.CommandTypes.Configure,
|
||||
arguments: {
|
||||
watchOptions: {
|
||||
fallbackPolling: protocol.PollingWatchKind.PriorityInterval
|
||||
}
|
||||
}
|
||||
});
|
||||
const service = session.getProjectService();
|
||||
openFilesForSession([{ file: commonFile1, projectRootPath: "/a/b" }], session);
|
||||
checkProjectActualFiles(
|
||||
service.configuredProjects.get(configFile.path)!,
|
||||
files.map(f => f.path).concat(commonFile1.path)
|
||||
);
|
||||
|
||||
const filePaths = files.map(f => f.path.toLowerCase());
|
||||
checkWatchedFilesDetailed(
|
||||
host,
|
||||
filePaths.concat(["/a/b", "/a/b/node_modules/@types"]),
|
||||
1,
|
||||
arrayToMap(
|
||||
filePaths.concat(["/a/b", "/a/b/node_modules/@types"]),
|
||||
identity,
|
||||
f => [contains(filePaths, f) ? PollingInterval.Low : PollingInterval.Medium]
|
||||
)
|
||||
);
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
|
||||
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user