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:
Sheetal Nandi 2019-12-11 13:26:44 -08:00 committed by GitHub
parent 4212484ae1
commit 236012e47b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 2417 additions and 720 deletions

View file

@ -754,7 +754,7 @@ namespace ts {
function convertToReusableCompilerOptions(options: CompilerOptions, relativeToBuildInfo: (path: string) => string) {
const result: CompilerOptions = {};
const optionsNameMap = getOptionNameMap().optionNameMap;
const { optionsNameMap } = getOptionsNameMap();
for (const name in options) {
if (hasProperty(options, name)) {
@ -1194,4 +1194,4 @@ namespace ts {
return Debug.assertDefined(state.program);
}
}
}
}

View file

@ -73,6 +73,49 @@ namespace ts {
/* @internal */
export const libMap = createMapFromEntries(libEntries);
// Watch related options
/* @internal */
export const optionsForWatch: CommandLineOption[] = [
{
name: "watchFile",
type: createMapFromTemplate({
fixedpollinginterval: WatchFileKind.FixedPollingInterval,
prioritypollinginterval: WatchFileKind.PriorityPollingInterval,
dynamicprioritypolling: WatchFileKind.DynamicPriorityPolling,
usefsevents: WatchFileKind.UseFsEvents,
usefseventsonparentdirectory: WatchFileKind.UseFsEventsOnParentDirectory,
}),
category: Diagnostics.Advanced_Options,
description: Diagnostics.Specify_strategy_for_watching_file_Colon_FixedPollingInterval_default_PriorityPollingInterval_DynamicPriorityPolling_UseFsEvents_UseFsEventsOnParentDirectory,
},
{
name: "watchDirectory",
type: createMapFromTemplate({
usefsevents: WatchDirectoryKind.UseFsEvents,
fixedpollinginterval: WatchDirectoryKind.FixedPollingInterval,
dynamicprioritypolling: WatchDirectoryKind.DynamicPriorityPolling,
}),
category: Diagnostics.Advanced_Options,
description: Diagnostics.Specify_strategy_for_watching_directory_on_platforms_that_don_t_support_recursive_watching_natively_Colon_UseFsEvents_default_FixedPollingInterval_DynamicPriorityPolling,
},
{
name: "fallbackPolling",
type: createMapFromTemplate({
fixedinterval: PollingWatchKind.FixedInterval,
priorityinterval: PollingWatchKind.PriorityInterval,
dynamicpriority: PollingWatchKind.DynamicPriority,
}),
category: Diagnostics.Advanced_Options,
description: Diagnostics.Specify_strategy_for_creating_a_polling_watch_when_it_fails_to_create_using_file_system_events_Colon_FixedInterval_default_PriorityInterval_DynamicPriority,
},
{
name: "synchronousWatchDirectory",
type: "boolean",
category: Diagnostics.Advanced_Options,
description: Diagnostics.Synchronously_call_callbacks_and_update_the_state_of_directory_watchers_on_platforms_that_don_t_support_recursive_watching_natively,
},
];
/* @internal */
export const commonOptionsWithBuild: CommandLineOption[] = [
{
@ -921,7 +964,7 @@ namespace ts {
type: "object"
},
description: Diagnostics.List_of_language_service_plugins
}
},
];
/* @internal */
@ -1008,11 +1051,32 @@ namespace ts {
];
/* @internal */
export interface OptionNameMap {
optionNameMap: Map<CommandLineOption>;
export interface OptionsNameMap {
optionsNameMap: Map<CommandLineOption>;
shortOptionNames: Map<string>;
}
/*@internal*/
export function createOptionNameMap(optionDeclarations: readonly CommandLineOption[]): OptionsNameMap {
const optionsNameMap = createMap<CommandLineOption>();
const shortOptionNames = createMap<string>();
forEach(optionDeclarations, option => {
optionsNameMap.set(option.name.toLowerCase(), option);
if (option.shortName) {
shortOptionNames.set(option.shortName, option.name);
}
});
return { optionsNameMap, shortOptionNames };
}
let optionsNameMapCache: OptionsNameMap;
/* @internal */
export function getOptionsNameMap(): OptionsNameMap {
return optionsNameMapCache || (optionsNameMapCache = createOptionNameMap(optionDeclarations));
}
/* @internal */
export const defaultInitCompilerOptions: CompilerOptions = {
module: ModuleKind.CommonJS,
@ -1022,8 +1086,6 @@ namespace ts {
forceConsistentCasingInFileNames: true
};
let optionNameMapCache: OptionNameMap;
/* @internal */
export function convertEnableAutoDiscoveryToEnable(typeAcquisition: TypeAcquisition): TypeAcquisition {
// Convert deprecated typingOptions.enableAutoDiscovery to typeAcquisition.enable
@ -1037,25 +1099,6 @@ namespace ts {
return typeAcquisition;
}
/* @internal */
export function getOptionNameMap(): OptionNameMap {
return optionNameMapCache || (optionNameMapCache = createOptionNameMap(optionDeclarations));
}
/*@internal*/
export function createOptionNameMap(optionDeclarations: readonly CommandLineOption[]): OptionNameMap {
const optionNameMap = createMap<CommandLineOption>();
const shortOptionNames = createMap<string>();
forEach(optionDeclarations, option => {
optionNameMap.set(option.name.toLowerCase(), option);
if (option.shortName) {
shortOptionNames.set(option.shortName, option.name);
}
});
return { optionNameMap, shortOptionNames };
}
/* @internal */
export function createCompilerDiagnosticForInvalidCustomType(opt: CommandLineOptionOfCustomType): Diagnostic {
return createDiagnosticForInvalidCustomType(opt, createCompilerDiagnostic);
@ -1092,27 +1135,43 @@ namespace ts {
}
interface OptionsBase {
[option: string]: CompilerOptionsValue | undefined;
[option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined;
}
interface ParseCommandLineWorkerDiagnostics {
unknownOptionDiagnostic: DiagnosticMessage,
unknownDidYouMeanDiagnostic: DiagnosticMessage,
optionTypeMismatchDiagnostic: DiagnosticMessage
interface ParseCommandLineWorkerDiagnostics extends DidYouMeanOptionsDiagnostics {
getOptionsNameMap: () => OptionsNameMap;
optionTypeMismatchDiagnostic: DiagnosticMessage;
}
function getOptionName(option: CommandLineOption) {
return option.name;
}
function createUnknownOptionError(
unknownOption: string,
diagnostics: DidYouMeanOptionsDiagnostics,
createDiagnostics: (message: DiagnosticMessage, arg0: string, arg1?: string) => Diagnostic,
unknownOptionErrorText?: string
) {
const possibleOption = getSpellingSuggestion(unknownOption, diagnostics.optionDeclarations, getOptionName);
return possibleOption ?
createDiagnostics(diagnostics.unknownDidYouMeanDiagnostic, unknownOptionErrorText || unknownOption, possibleOption.name) :
createDiagnostics(diagnostics.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption);
}
function parseCommandLineWorker(
getOptionNameMap: () => OptionNameMap,
diagnostics: ParseCommandLineWorkerDiagnostics,
commandLine: readonly string[],
readFile?: (path: string) => string | undefined) {
const options = {} as OptionsBase;
let watchOptions: WatchOptions | undefined;
const fileNames: string[] = [];
const errors: Diagnostic[] = [];
parseStrings(commandLine);
return {
options,
watchOptions,
fileNames,
errors
};
@ -1126,57 +1185,18 @@ namespace ts {
parseResponseFile(s.slice(1));
}
else if (s.charCodeAt(0) === CharacterCodes.minus) {
const opt = getOptionDeclarationFromName(getOptionNameMap, s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1), /*allowShort*/ true);
const inputOptionName = s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1);
const opt = getOptionDeclarationFromName(diagnostics.getOptionsNameMap, inputOptionName, /*allowShort*/ true);
if (opt) {
if (opt.isTSConfigOnly) {
errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file, opt.name));
}
else {
// Check to see if no argument was provided (e.g. "--locale" is the last command-line argument).
if (!args[i] && opt.type !== "boolean") {
errors.push(createCompilerDiagnostic(diagnostics.optionTypeMismatchDiagnostic, opt.name));
}
switch (opt.type) {
case "number":
options[opt.name] = parseInt(args[i]);
i++;
break;
case "boolean":
// boolean flag has optional value true, false, others
const optValue = args[i];
options[opt.name] = optValue !== "false";
// consume next argument as boolean flag value
if (optValue === "false" || optValue === "true") {
i++;
}
break;
case "string":
options[opt.name] = args[i] || "";
i++;
break;
case "list":
const result = parseListTypeOption(opt, args[i], errors);
options[opt.name] = result || [];
if (result) {
i++;
}
break;
// If not a primitive, the possible types are specified in what is effectively a map of options.
default:
options[opt.name] = parseCustomTypeOption(<CommandLineOptionOfCustomType>opt, args[i], errors);
i++;
break;
}
}
i = parseOptionValue(args, i, diagnostics, opt, options, errors);
}
else {
const possibleOption = getSpellingSuggestion(s, optionDeclarations, opt => `--${opt.name}`);
if (possibleOption) {
errors.push(createCompilerDiagnostic(diagnostics.unknownDidYouMeanDiagnostic, s, possibleOption.name));
const watchOpt = getOptionDeclarationFromName(watchOptionsDidYouMeanDiagnostics.getOptionsNameMap, inputOptionName, /*allowShort*/ true);
if (watchOpt) {
i = parseOptionValue(args, i, watchOptionsDidYouMeanDiagnostics, watchOpt, watchOptions || (watchOptions = {}), errors);
}
else {
errors.push(createCompilerDiagnostic(diagnostics.unknownOptionDiagnostic, s));
errors.push(createUnknownOptionError(inputOptionName, diagnostics, createCompilerDiagnostic, s));
}
}
}
@ -1220,23 +1240,77 @@ namespace ts {
}
}
const compilerOptionsDefaultDiagnostics = {
function parseOptionValue(
args: readonly string[],
i: number,
diagnostics: ParseCommandLineWorkerDiagnostics,
opt: CommandLineOption,
options: OptionsBase,
errors: Diagnostic[]
) {
if (opt.isTSConfigOnly) {
errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file, opt.name));
}
else {
// Check to see if no argument was provided (e.g. "--locale" is the last command-line argument).
if (!args[i] && opt.type !== "boolean") {
errors.push(createCompilerDiagnostic(diagnostics.optionTypeMismatchDiagnostic, opt.name, getCompilerOptionValueTypeString(opt)));
}
switch (opt.type) {
case "number":
options[opt.name] = parseInt(args[i]);
i++;
break;
case "boolean":
// boolean flag has optional value true, false, others
const optValue = args[i];
options[opt.name] = optValue !== "false";
// consume next argument as boolean flag value
if (optValue === "false" || optValue === "true") {
i++;
}
break;
case "string":
options[opt.name] = args[i] || "";
i++;
break;
case "list":
const result = parseListTypeOption(opt, args[i], errors);
options[opt.name] = result || [];
if (result) {
i++;
}
break;
// If not a primitive, the possible types are specified in what is effectively a map of options.
default:
options[opt.name] = parseCustomTypeOption(<CommandLineOptionOfCustomType>opt, args[i], errors);
i++;
break;
}
}
return i;
}
const compilerOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = {
getOptionsNameMap,
optionDeclarations,
unknownOptionDiagnostic: Diagnostics.Unknown_compiler_option_0,
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1,
optionTypeMismatchDiagnostic: Diagnostics.Compiler_option_0_expects_an_argument
};
export function parseCommandLine(commandLine: readonly string[], readFile?: (path: string) => string | undefined): ParsedCommandLine {
return parseCommandLineWorker(getOptionNameMap, compilerOptionsDefaultDiagnostics, commandLine, readFile);
return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile);
}
/** @internal */
export function getOptionFromName(optionName: string, allowShort?: boolean): CommandLineOption | undefined {
return getOptionDeclarationFromName(getOptionNameMap, optionName, allowShort);
return getOptionDeclarationFromName(getOptionsNameMap, optionName, allowShort);
}
function getOptionDeclarationFromName(getOptionNameMap: () => OptionNameMap, optionName: string, allowShort = false): CommandLineOption | undefined {
function getOptionDeclarationFromName(getOptionNameMap: () => OptionsNameMap, optionName: string, allowShort = false): CommandLineOption | undefined {
optionName = optionName.toLowerCase();
const { optionNameMap, shortOptionNames } = getOptionNameMap();
const { optionsNameMap, shortOptionNames } = getOptionNameMap();
// Try to translate short option names to their full equivalents.
if (allowShort) {
const short = shortOptionNames.get(optionName);
@ -1244,25 +1318,36 @@ namespace ts {
optionName = short;
}
}
return optionNameMap.get(optionName);
return optionsNameMap.get(optionName);
}
/*@internal*/
export interface ParsedBuildCommand {
buildOptions: BuildOptions;
watchOptions: WatchOptions | undefined;
projects: string[];
errors: Diagnostic[];
}
let buildOptionsNameMapCache: OptionsNameMap;
function getBuildOptionsNameMap(): OptionsNameMap {
return buildOptionsNameMapCache || (buildOptionsNameMapCache = createOptionNameMap(buildOpts));
}
const buildOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = {
getOptionsNameMap: getBuildOptionsNameMap,
optionDeclarations: buildOpts,
unknownOptionDiagnostic: Diagnostics.Unknown_build_option_0,
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_build_option_0_Did_you_mean_1,
optionTypeMismatchDiagnostic: Diagnostics.Build_option_0_requires_a_value_of_type_1
};
/*@internal*/
export function parseBuildCommand(args: readonly string[]): ParsedBuildCommand {
let buildOptionNameMap: OptionNameMap | undefined;
const returnBuildOptionNameMap = () => (buildOptionNameMap || (buildOptionNameMap = createOptionNameMap(buildOpts)));
const { options, fileNames: projects, errors } = parseCommandLineWorker(returnBuildOptionNameMap, {
unknownOptionDiagnostic: Diagnostics.Unknown_build_option_0,
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_build_option_0_Did_you_mean_1,
optionTypeMismatchDiagnostic: Diagnostics.Build_option_0_requires_a_value_of_type_1
}, args);
const { options, watchOptions, fileNames: projects, errors } = parseCommandLineWorker(
buildOptionsDidYouMeanDiagnostics,
args
);
const buildOptions = options as BuildOptions;
if (projects.length === 0) {
@ -1284,7 +1369,7 @@ namespace ts {
errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "watch", "dry"));
}
return { buildOptions, projects, errors };
return { buildOptions, watchOptions, projects, errors };
}
/* @internal */
@ -1318,7 +1403,8 @@ namespace ts {
configFileName: string,
optionsToExtend: CompilerOptions,
host: ParseConfigFileHost,
extendedConfigCache?: Map<ExtendedConfigCacheEntry>
extendedConfigCache?: Map<ExtendedConfigCacheEntry>,
watchOptionsToExtend?: WatchOptions
): ParsedCommandLine | undefined {
let configFileText: string | undefined;
try {
@ -1348,7 +1434,8 @@ namespace ts {
getNormalizedAbsolutePath(configFileName, cwd),
/*resolutionStack*/ undefined,
/*extraFileExtension*/ undefined,
extendedConfigCache
extendedConfigCache,
watchOptionsToExtend
);
}
@ -1395,7 +1482,38 @@ namespace ts {
}
function commandLineOptionsToMap(options: readonly CommandLineOption[]) {
return arrayToMap(options, option => option.name);
return arrayToMap(options, getOptionName);
}
const typeAcquisitionDidYouMeanDiagnostics: DidYouMeanOptionsDiagnostics = {
optionDeclarations: typeAcquisitionDeclarations,
unknownOptionDiagnostic: Diagnostics.Unknown_type_acquisition_option_0,
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1,
};
let watchOptionsNameMapCache: OptionsNameMap;
function getWatchOptionsNameMap(): OptionsNameMap {
return watchOptionsNameMapCache || (watchOptionsNameMapCache = createOptionNameMap(optionsForWatch));
}
const watchOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = {
getOptionsNameMap: getWatchOptionsNameMap,
optionDeclarations: optionsForWatch,
unknownOptionDiagnostic: Diagnostics.Unknown_watch_option_0,
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_watch_option_0_Did_you_mean_1,
optionTypeMismatchDiagnostic: Diagnostics.Watch_option_0_requires_a_value_of_type_1
};
let commandLineCompilerOptionsMapCache: Map<CommandLineOption>;
function getCommandLineCompilerOptionsMap() {
return commandLineCompilerOptionsMapCache || (commandLineCompilerOptionsMapCache = commandLineOptionsToMap(optionDeclarations));
}
let commandLineWatchOptionsMapCache: Map<CommandLineOption>;
function getCommandLineWatchOptionsMap() {
return commandLineWatchOptionsMapCache || (commandLineWatchOptionsMapCache = commandLineOptionsToMap(optionsForWatch));
}
let commandLineTypeAcquisitionMapCache: Map<CommandLineOption>;
function getCommandLineTypeAcquisitionMap() {
return commandLineTypeAcquisitionMapCache || (commandLineTypeAcquisitionMapCache = commandLineOptionsToMap(typeAcquisitionDeclarations));
}
let _tsconfigRootOptions: TsConfigOnlyOption;
@ -1408,29 +1526,26 @@ namespace ts {
{
name: "compilerOptions",
type: "object",
elementOptions: commandLineOptionsToMap(optionDeclarations),
extraKeyDiagnostics: {
unknownOptionDiagnostic: Diagnostics.Unknown_compiler_option_0,
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1
},
elementOptions: getCommandLineCompilerOptionsMap(),
extraKeyDiagnostics: compilerOptionsDidYouMeanDiagnostics,
},
{
name: "watchOptions",
type: "object",
elementOptions: getCommandLineWatchOptionsMap(),
extraKeyDiagnostics: watchOptionsDidYouMeanDiagnostics,
},
{
name: "typingOptions",
type: "object",
elementOptions: commandLineOptionsToMap(typeAcquisitionDeclarations),
extraKeyDiagnostics: {
unknownOptionDiagnostic: Diagnostics.Unknown_type_acquisition_option_0,
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1
},
elementOptions: getCommandLineTypeAcquisitionMap(),
extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics,
},
{
name: "typeAcquisition",
type: "object",
elementOptions: commandLineOptionsToMap(typeAcquisitionDeclarations),
extraKeyDiagnostics: {
unknownOptionDiagnostic: Diagnostics.Unknown_type_acquisition_option_0,
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1
}
elementOptions: getCommandLineTypeAcquisitionMap(),
extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics
},
{
name: "extends",
@ -1536,7 +1651,7 @@ namespace ts {
function convertObjectLiteralExpressionToJson(
node: ObjectLiteralExpression,
knownOptions: Map<CommandLineOption> | undefined,
extraKeyDiagnostics: DidYouMeanOptionalDiagnostics | undefined,
extraKeyDiagnostics: DidYouMeanOptionsDiagnostics | undefined,
parentOption: string | undefined
): any {
const result: any = returnValue ? {} : undefined;
@ -1558,13 +1673,11 @@ namespace ts {
const option = keyText && knownOptions ? knownOptions.get(keyText) : undefined;
if (keyText && extraKeyDiagnostics && !option) {
if (knownOptions) {
const possibleOption = getSpellingSuggestion(keyText, arrayFrom(knownOptions.keys()), identity);
if (possibleOption) {
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnostics.unknownDidYouMeanDiagnostic, keyText, possibleOption));
}
else {
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnostics.unknownOptionDiagnostic, keyText));
}
errors.push(createUnknownOptionError(
keyText,
extraKeyDiagnostics,
(message, arg0, arg1) => createDiagnosticForNodeInSourceFile(sourceFile, element.name, message, arg0, arg1)
));
}
else {
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnostics.unknownOptionDiagnostic, keyText));
@ -1765,9 +1878,10 @@ namespace ts {
f => getRelativePathFromFile(getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), getNormalizedAbsolutePath(f, host.getCurrentDirectory()), getCanonicalFileName)
);
const optionMap = serializeCompilerOptions(configParseResult.options, { configFilePath: getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), useCaseSensitiveFileNames: host.useCaseSensitiveFileNames });
const watchOptionMap = configParseResult.watchOptions && serializeWatchOptions(configParseResult.watchOptions);
const config = {
compilerOptions: {
...arrayFrom(optionMap.entries()).reduce((prev, cur) => ({ ...prev, [cur[0]]: cur[1] }), {}),
...optionMapToObject(optionMap),
showConfig: undefined,
configFile: undefined,
configFilePath: undefined,
@ -1779,6 +1893,7 @@ namespace ts {
build: undefined,
version: undefined,
},
watchOptions: watchOptionMap && optionMapToObject(watchOptionMap),
references: map(configParseResult.projectReferences, r => ({ ...r, path: r.originalPath ? r.originalPath : "", originalPath: undefined })),
files: length(files) ? files : undefined,
...(configParseResult.configFileSpecs ? {
@ -1790,6 +1905,12 @@ namespace ts {
return config;
}
function optionMapToObject(optionMap: Map<CompilerOptionsValue>): object {
return {
...arrayFrom(optionMap.entries()).reduce((prev, cur) => ({ ...prev, [cur[0]]: cur[1] }), {}),
};
}
function filterSameAsDefaultInclude(specs: readonly string[] | undefined) {
if (!length(specs)) return undefined;
if (length(specs) !== 1) return specs;
@ -1836,9 +1957,23 @@ namespace ts {
});
}
function serializeCompilerOptions(options: CompilerOptions, pathOptions?: { configFilePath: string, useCaseSensitiveFileNames: boolean }): Map<CompilerOptionsValue> {
function serializeCompilerOptions(
options: CompilerOptions,
pathOptions?: { configFilePath: string, useCaseSensitiveFileNames: boolean }
): Map<CompilerOptionsValue> {
return serializeOptionBaseObject(options, getOptionsNameMap(), pathOptions);
}
function serializeWatchOptions(options: WatchOptions) {
return serializeOptionBaseObject(options, getWatchOptionsNameMap());
}
function serializeOptionBaseObject(
options: OptionsBase,
{ optionsNameMap }: OptionsNameMap,
pathOptions?: { configFilePath: string, useCaseSensitiveFileNames: boolean }
): Map<CompilerOptionsValue> {
const result = createMap<CompilerOptionsValue>();
const optionsNameMap = getOptionNameMap().optionNameMap;
const getCanonicalFileName = pathOptions && createGetCanonicalFileName(pathOptions.useCaseSensitiveFileNames);
for (const name in options) {
@ -1987,7 +2122,7 @@ namespace ts {
/* @internal */
export function convertToOptionsWithAbsolutePaths(options: CompilerOptions, toAbsolutePath: (path: string) => string) {
const result: CompilerOptions = {};
const optionsNameMap = getOptionNameMap().optionNameMap;
const optionsNameMap = getOptionsNameMap().optionsNameMap;
for (const name in options) {
if (hasProperty(options, name)) {
@ -2026,8 +2161,8 @@ namespace ts {
* @param basePath A root directory to resolve relative path entries in the config
* file to. e.g. outDir
*/
export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map<ExtendedConfigCacheEntry>): ParsedCommandLine {
return parseJsonConfigFileContentWorker(json, /*sourceFile*/ undefined, host, basePath, existingOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache);
export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map<ExtendedConfigCacheEntry>, existingWatchOptions?: WatchOptions): ParsedCommandLine {
return parseJsonConfigFileContentWorker(json, /*sourceFile*/ undefined, host, basePath, existingOptions, existingWatchOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache);
}
/**
@ -2037,8 +2172,8 @@ namespace ts {
* @param basePath A root directory to resolve relative path entries in the config
* file to. e.g. outDir
*/
export function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map<ExtendedConfigCacheEntry>): ParsedCommandLine {
return parseJsonConfigFileContentWorker(/*json*/ undefined, sourceFile, host, basePath, existingOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache);
export function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map<ExtendedConfigCacheEntry>, existingWatchOptions?: WatchOptions): ParsedCommandLine {
return parseJsonConfigFileContentWorker(/*json*/ undefined, sourceFile, host, basePath, existingOptions, existingWatchOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache);
}
/*@internal*/
@ -2073,6 +2208,7 @@ namespace ts {
host: ParseConfigHost,
basePath: string,
existingOptions: CompilerOptions = {},
existingWatchOptions: WatchOptions | undefined,
configFileName?: string,
resolutionStack: Path[] = [],
extraFileExtensions: readonly FileExtensionInfo[] = [],
@ -2084,12 +2220,17 @@ namespace ts {
const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors, extendedConfigCache);
const { raw } = parsedConfig;
const options = extend(existingOptions, parsedConfig.options || {});
const watchOptions = existingWatchOptions && parsedConfig.watchOptions ?
extend(existingWatchOptions, parsedConfig.watchOptions) :
parsedConfig.watchOptions || existingWatchOptions;
options.configFilePath = configFileName && normalizeSlashes(configFileName);
setConfigFileInOptions(options, sourceFile);
let projectReferences: ProjectReference[] | undefined;
const { fileNames, wildcardDirectories, spec } = getFileNames();
return {
options,
watchOptions,
fileNames,
projectReferences,
typeAcquisition: parsedConfig.typeAcquisition || getDefaultTypeAcquisition(),
@ -2232,6 +2373,7 @@ namespace ts {
export interface ParsedTsconfig {
raw: any;
options?: CompilerOptions;
watchOptions?: WatchOptions;
typeAcquisition?: TypeAcquisition;
/**
* Note that the case of the config path has not yet been normalized, as no files have been imported into the project yet
@ -2289,6 +2431,9 @@ namespace ts {
raw.compileOnSave = baseRaw.compileOnSave;
}
ownConfig.options = assign({}, extendedConfig.options, ownConfig.options);
ownConfig.watchOptions = ownConfig.watchOptions && extendedConfig.watchOptions ?
assign({}, extendedConfig.watchOptions, ownConfig.watchOptions) :
ownConfig.watchOptions || extendedConfig.watchOptions;
// TODO extend type typeAcquisition
}
}
@ -2311,6 +2456,7 @@ namespace ts {
// typingOptions has been deprecated and is only supported for backward compatibility purposes.
// It should be removed in future releases - use typeAcquisition instead.
const typeAcquisition = convertTypeAcquisitionFromJsonWorker(json.typeAcquisition || json.typingOptions, basePath, errors, configFileName);
const watchOptions = convertWatchOptionsFromJsonWorker(json.watchOptions, basePath, errors);
json.compileOnSave = convertCompileOnSaveOptionFromJson(json, basePath, errors);
let extendedConfigPath: string | undefined;
@ -2323,7 +2469,7 @@ namespace ts {
extendedConfigPath = getExtendsConfigPath(json.extends, host, newBase, errors, createCompilerDiagnostic);
}
}
return { raw: json, options, typeAcquisition, extendedConfigPath };
return { raw: json, options, watchOptions, typeAcquisition, extendedConfigPath };
}
function parseOwnConfigOfJsonSourceFile(
@ -2335,16 +2481,28 @@ namespace ts {
): ParsedTsconfig {
const options = getDefaultCompilerOptions(configFileName);
let typeAcquisition: TypeAcquisition | undefined, typingOptionstypeAcquisition: TypeAcquisition | undefined;
let watchOptions: WatchOptions | undefined;
let extendedConfigPath: string | undefined;
const optionsIterator: JsonConversionNotifier = {
onSetValidOptionKeyValueInParent(parentOption: string, option: CommandLineOption, value: CompilerOptionsValue) {
Debug.assert(parentOption === "compilerOptions" || parentOption === "typeAcquisition" || parentOption === "typingOptions");
const currentOption = parentOption === "compilerOptions" ?
options :
parentOption === "typeAcquisition" ?
(typeAcquisition || (typeAcquisition = getDefaultTypeAcquisition(configFileName))) :
(typingOptionstypeAcquisition || (typingOptionstypeAcquisition = getDefaultTypeAcquisition(configFileName)));
let currentOption;
switch (parentOption) {
case "compilerOptions":
currentOption = options;
break;
case "watchOptions":
currentOption = (watchOptions || (watchOptions = {}));
break;
case "typeAcquisition":
currentOption = (typeAcquisition || (typeAcquisition = getDefaultTypeAcquisition(configFileName)));
break;
case "typingOptions":
currentOption = (typingOptionstypeAcquisition || (typingOptionstypeAcquisition = getDefaultTypeAcquisition(configFileName)));
break;
default:
Debug.fail("Unknown option");
}
currentOption[option.name] = normalizeOptionValue(option, basePath, value);
},
@ -2386,7 +2544,7 @@ namespace ts {
}
}
return { raw: json, options, typeAcquisition, extendedConfigPath };
return { raw: json, options, watchOptions, typeAcquisition, extendedConfigPath };
}
function getExtendsConfigPath(
@ -2508,7 +2666,7 @@ namespace ts {
basePath: string, errors: Push<Diagnostic>, configFileName?: string): CompilerOptions {
const options = getDefaultCompilerOptions(configFileName);
convertOptionsFromJson(optionDeclarations, jsonOptions, basePath, options, compilerOptionsDefaultDiagnostics, errors);
convertOptionsFromJson(getCommandLineCompilerOptionsMap(), jsonOptions, basePath, options, compilerOptionsDidYouMeanDiagnostics, errors);
if (configFileName) {
options.configFilePath = normalizeSlashes(configFileName);
}
@ -2525,40 +2683,35 @@ namespace ts {
const options = getDefaultTypeAcquisition(configFileName);
const typeAcquisition = convertEnableAutoDiscoveryToEnable(jsonOptions);
const diagnostics = {
unknownOptionDiagnostic: Diagnostics.Unknown_type_acquisition_option_0,
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1 ,
};
convertOptionsFromJson(typeAcquisitionDeclarations, typeAcquisition, basePath, options, diagnostics, errors);
convertOptionsFromJson(getCommandLineTypeAcquisitionMap(), typeAcquisition, basePath, options, typeAcquisitionDidYouMeanDiagnostics, errors);
return options;
}
function convertWatchOptionsFromJsonWorker(jsonOptions: any, basePath: string, errors: Push<Diagnostic>): WatchOptions | undefined {
return convertOptionsFromJson(getCommandLineWatchOptionsMap(), jsonOptions, basePath, /*defaultOptions*/ undefined, watchOptionsDidYouMeanDiagnostics, errors);
}
function convertOptionsFromJson(optionDeclarations: readonly CommandLineOption[], jsonOptions: any, basePath: string,
defaultOptions: CompilerOptions | TypeAcquisition, diagnostics: DidYouMeanOptionalDiagnostics, errors: Push<Diagnostic>) {
function convertOptionsFromJson(optionsNameMap: Map<CommandLineOption>, jsonOptions: any, basePath: string,
defaultOptions: undefined, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push<Diagnostic>): WatchOptions | undefined;
function convertOptionsFromJson(optionsNameMap: Map<CommandLineOption>, jsonOptions: any, basePath: string,
defaultOptions: CompilerOptions | TypeAcquisition, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push<Diagnostic>): CompilerOptions | TypeAcquisition;
function convertOptionsFromJson(optionsNameMap: Map<CommandLineOption>, jsonOptions: any, basePath: string,
defaultOptions: CompilerOptions | TypeAcquisition | WatchOptions | undefined, diagnostics: DidYouMeanOptionsDiagnostics, errors: Push<Diagnostic>) {
if (!jsonOptions) {
return;
}
const optionNameMap = commandLineOptionsToMap(optionDeclarations);
for (const id in jsonOptions) {
const opt = optionNameMap.get(id);
const opt = optionsNameMap.get(id);
if (opt) {
defaultOptions[opt.name] = convertJsonOption(opt, jsonOptions[id], basePath, errors);
(defaultOptions || (defaultOptions = {}))[opt.name] = convertJsonOption(opt, jsonOptions[id], basePath, errors);
}
else {
const possibleOption = getSpellingSuggestion(id, <CommandLineOption[]>optionDeclarations, opt => opt.name);
if (possibleOption) {
errors.push(createCompilerDiagnostic(diagnostics.unknownDidYouMeanDiagnostic, id, possibleOption.name));
}
else {
errors.push(createCompilerDiagnostic(diagnostics.unknownOptionDiagnostic, id));
}
errors.push(createUnknownOptionError(id, diagnostics, createCompilerDiagnostic));
}
}
return defaultOptions;
}
function convertJsonOption(opt: CommandLineOption, value: any, basePath: string, errors: Push<Diagnostic>): CompilerOptionsValue {

View file

@ -3313,6 +3313,18 @@
"category": "Error",
"code": 5077
},
"Unknown watch option '{0}'.": {
"category": "Error",
"code": 5078
},
"Unknown watch option '{0}'. Did you mean '{1}'?": {
"category": "Error",
"code": 5079
},
"Watch option '{0}' requires a value of type {1}.": {
"category": "Error",
"code": 5080
},
"Generates a sourcemap for each corresponding '.d.ts' file.": {
"category": "Message",
@ -4160,6 +4172,22 @@
"category": "Message",
"code": 6224
},
"Specify strategy for watching file: 'FixedPollingInterval' (default), 'PriorityPollingInterval', 'DynamicPriorityPolling', 'UseFsEvents', 'UseFsEventsOnParentDirectory'.": {
"category": "Message",
"code": 6225
},
"Specify strategy for watching directory on platforms that don't support recursive watching natively: 'UseFsEvents' (default), 'FixedPollingInterval', 'DynamicPriorityPolling'.": {
"category": "Message",
"code": 6226
},
"Specify strategy for creating a polling watch when it fails to create using file system events: 'FixedInterval' (default), 'PriorityInterval', 'DynamicPriority'.": {
"category": "Message",
"code": 6227
},
"Synchronously call callbacks and update the state of directory watchers on platforms that don't support recursive watching natively.": {
"category": "Message",
"code": 6228
},
"Projects to reference": {
"category": "Message",

View file

@ -50,9 +50,9 @@ namespace ts {
}
/* @internal */
export type HostWatchFile = (fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval | undefined) => FileWatcher;
export type HostWatchFile = (fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined) => FileWatcher;
/* @internal */
export type HostWatchDirectory = (fileName: string, callback: DirectoryWatcherCallback, recursive?: boolean) => FileWatcher;
export type HostWatchDirectory = (fileName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined) => FileWatcher;
/* @internal */
export const missingFileModifiedTime = new Date(0); // Any subsequent modification will occur after this time
@ -127,7 +127,10 @@ namespace ts {
}
/* @internal */
export function createDynamicPriorityPollingWatchFile(host: { getModifiedTime: System["getModifiedTime"]; setTimeout: System["setTimeout"]; }): HostWatchFile {
export function createDynamicPriorityPollingWatchFile(host: {
getModifiedTime: NonNullable<System["getModifiedTime"]>;
setTimeout: NonNullable<System["setTimeout"]>;
}): HostWatchFile {
interface WatchedFile extends ts.WatchedFile {
isClosed?: boolean;
unchangedPolls: number;
@ -296,11 +299,65 @@ namespace ts {
}
function scheduleNextPoll(pollingInterval: PollingInterval) {
pollingIntervalQueue(pollingInterval).pollScheduled = host.setTimeout!(pollingInterval === PollingInterval.Low ? pollLowPollingIntervalQueue : pollPollingIntervalQueue, pollingInterval, pollingIntervalQueue(pollingInterval));
pollingIntervalQueue(pollingInterval).pollScheduled = host.setTimeout(pollingInterval === PollingInterval.Low ? pollLowPollingIntervalQueue : pollPollingIntervalQueue, pollingInterval, pollingIntervalQueue(pollingInterval));
}
function getModifiedTime(fileName: string) {
return host.getModifiedTime!(fileName) || missingFileModifiedTime;
return host.getModifiedTime(fileName) || missingFileModifiedTime;
}
}
function createUseFsEventsOnParentDirectoryWatchFile(fsWatch: FsWatch, useCaseSensitiveFileNames: boolean): HostWatchFile {
// One file can have multiple watchers
const fileWatcherCallbacks = createMultiMap<FileWatcherCallback>();
const dirWatchers = createMap<DirectoryWatcher>();
const toCanonicalName = createGetCanonicalFileName(useCaseSensitiveFileNames);
return nonPollingWatchFile;
function nonPollingWatchFile(fileName: string, callback: FileWatcherCallback, _pollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined): FileWatcher {
const filePath = toCanonicalName(fileName);
fileWatcherCallbacks.add(filePath, callback);
const dirPath = getDirectoryPath(filePath) || ".";
const watcher = dirWatchers.get(dirPath) ||
createDirectoryWatcher(getDirectoryPath(fileName) || ".", dirPath, fallbackOptions);
watcher.referenceCount++;
return {
close: () => {
if (watcher.referenceCount === 1) {
watcher.close();
dirWatchers.delete(dirPath);
}
else {
watcher.referenceCount--;
}
fileWatcherCallbacks.remove(filePath, callback);
}
};
}
function createDirectoryWatcher(dirName: string, dirPath: string, fallbackOptions: WatchOptions | undefined) {
const watcher = fsWatch(
dirName,
FileSystemEntryKind.Directory,
(_eventName: string, relativeFileName) => {
// When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined"
if (!isString(relativeFileName)) { return; }
const fileName = getNormalizedAbsolutePath(relativeFileName, dirName);
// Some applications save a working file via rename operations
const callbacks = fileName && fileWatcherCallbacks.get(toCanonicalName(fileName));
if (callbacks) {
for (const fileCallback of callbacks) {
fileCallback(fileName, FileWatcherEventKind.Changed);
}
}
},
/*recursive*/ false,
PollingInterval.Medium,
fallbackOptions
) as DirectoryWatcher;
watcher.referenceCount = 0;
dirWatchers.set(dirPath, watcher);
return watcher;
}
}
@ -317,7 +374,7 @@ namespace ts {
const callbacksCache = createMultiMap<FileWatcherCallback>();
const toCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
return (fileName, callback, pollingInterval) => {
return (fileName, callback, pollingInterval, options) => {
const path = toCanonicalFileName(fileName);
const existing = cache.get(path);
if (existing) {
@ -331,7 +388,8 @@ namespace ts {
callbacksCache.get(path),
cb => cb(fileName, eventKind)
),
pollingInterval
pollingInterval,
options
),
refCount: 1
});
@ -394,6 +452,8 @@ namespace ts {
getAccessibleSortedChildDirectories(path: string): readonly string[];
directoryExists(dir: string): boolean;
realpath(s: string): string;
setTimeout: NonNullable<System["setTimeout"]>;
clearTimeout: NonNullable<System["clearTimeout"]>;
}
/**
@ -402,7 +462,7 @@ namespace ts {
* (eg on OS that dont support recursive watch using fs.watch use fs.watchFile)
*/
/*@internal*/
export function createRecursiveDirectoryWatcher(host: RecursiveDirectoryWatcherHost): (directoryName: string, callback: DirectoryWatcherCallback) => FileWatcher {
export function createDirectoryWatcherSupportingRecursive(host: RecursiveDirectoryWatcherHost): HostWatchDirectory {
interface ChildDirectoryWatcher extends FileWatcher {
dirName: string;
}
@ -414,16 +474,21 @@ namespace ts {
}
const cache = createMap<HostDirectoryWatcher>();
const callbackCache = createMultiMap<DirectoryWatcherCallback>();
const callbackCache = createMultiMap<{ dirName: string; callback: DirectoryWatcherCallback; }>();
const cacheToUpdateChildWatches = createMap<{ dirName: string; options: WatchOptions | undefined; }>();
let timerToUpdateChildWatches: any;
const filePathComparer = getStringComparer(!host.useCaseSensitiveFileNames);
const toCanonicalFilePath = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
return createDirectoryWatcher;
return (dirName, callback, recursive, options) => recursive ?
createDirectoryWatcher(dirName, options, callback) :
host.watchDirectory(dirName, callback, recursive, options);
/**
* Create the directory watcher for the dirPath.
*/
function createDirectoryWatcher(dirName: string, callback?: DirectoryWatcherCallback): ChildDirectoryWatcher {
function createDirectoryWatcher(dirName: string, options: WatchOptions | undefined, callback?: DirectoryWatcherCallback): ChildDirectoryWatcher {
const dirPath = toCanonicalFilePath(dirName) as Path;
let directoryWatcher = cache.get(dirPath);
if (directoryWatcher) {
@ -434,32 +499,34 @@ namespace ts {
watcher: host.watchDirectory(dirName, fileName => {
if (isIgnoredPath(fileName)) return;
// Call the actual callback
callbackCache.forEach((callbacks, rootDirName) => {
if (rootDirName === dirPath || (startsWith(dirPath, rootDirName) && dirPath[rootDirName.length] === directorySeparator)) {
callbacks.forEach(callback => callback(fileName));
}
});
if (options?.synchronousWatchDirectory) {
// Call the actual callback
invokeCallbacks(dirPath, fileName);
// Iterate through existing children and update the watches if needed
updateChildWatches(dirName, dirPath);
}),
// Iterate through existing children and update the watches if needed
updateChildWatches(dirName, dirPath, options);
}
else {
nonSyncUpdateChildWatches(dirName, dirPath, fileName, options);
}
}, /*recursive*/ false, options),
refCount: 1,
childWatches: emptyArray
};
cache.set(dirPath, directoryWatcher);
updateChildWatches(dirName, dirPath);
updateChildWatches(dirName, dirPath, options);
}
if (callback) {
callbackCache.add(dirPath, callback);
const callbackToAdd = callback && { dirName, callback };
if (callbackToAdd) {
callbackCache.add(dirPath, callbackToAdd);
}
return {
dirName,
close: () => {
const directoryWatcher = Debug.assertDefined(cache.get(dirPath));
if (callback) callbackCache.remove(dirPath, callback);
if (callbackToAdd) callbackCache.remove(dirPath, callbackToAdd);
directoryWatcher.refCount--;
if (directoryWatcher.refCount) return;
@ -471,18 +538,103 @@ namespace ts {
};
}
function updateChildWatches(dirName: string, dirPath: Path) {
function invokeCallbacks(dirPath: Path, fileNameOrInvokeMap: string | Map<true>) {
let fileName: string | undefined;
let invokeMap: Map<true> | undefined;
if (isString(fileNameOrInvokeMap)) {
fileName = fileNameOrInvokeMap;
}
else {
invokeMap = fileNameOrInvokeMap;
}
// Call the actual callback
callbackCache.forEach((callbacks, rootDirName) => {
if (invokeMap && invokeMap.has(rootDirName)) return;
if (rootDirName === dirPath || (startsWith(dirPath, rootDirName) && dirPath[rootDirName.length] === directorySeparator)) {
if (invokeMap) {
invokeMap.set(rootDirName, true);
}
else {
callbacks.forEach(({ callback }) => callback(fileName!));
}
}
});
}
function nonSyncUpdateChildWatches(dirName: string, dirPath: Path, fileName: string, options: WatchOptions | undefined) {
// Iterate through existing children and update the watches if needed
const parentWatcher = cache.get(dirPath);
if (parentWatcher && host.directoryExists(dirName)) {
// Schedule the update and postpone invoke for callbacks
scheduleUpdateChildWatches(dirName, dirPath, options);
return;
}
// Call the actual callbacks and remove child watches
invokeCallbacks(dirPath, fileName);
removeChildWatches(parentWatcher);
}
function scheduleUpdateChildWatches(dirName: string, dirPath: Path, options: WatchOptions | undefined) {
if (!cacheToUpdateChildWatches.has(dirPath)) {
cacheToUpdateChildWatches.set(dirPath, { dirName, options });
}
if (timerToUpdateChildWatches) {
host.clearTimeout(timerToUpdateChildWatches);
timerToUpdateChildWatches = undefined;
}
timerToUpdateChildWatches = host.setTimeout(onTimerToUpdateChildWatches, 1000);
}
function onTimerToUpdateChildWatches() {
timerToUpdateChildWatches = undefined;
sysLog(`sysLog:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size}`);
const start = timestamp();
const invokeMap = createMap<true>();
while (!timerToUpdateChildWatches && cacheToUpdateChildWatches.size) {
const { value: [dirPath, { dirName, options }], done } = cacheToUpdateChildWatches.entries().next();
Debug.assert(!done);
cacheToUpdateChildWatches.delete(dirPath);
// Because the child refresh is fresh, we would need to invalidate whole root directory being watched
// to ensure that all the changes are reflected at this time
invokeCallbacks(dirPath as Path, invokeMap);
updateChildWatches(dirName, dirPath as Path, options);
}
sysLog(`sysLog:: invokingWatchers:: ${timestamp() - start}ms:: ${cacheToUpdateChildWatches.size}`);
callbackCache.forEach((callbacks, rootDirName) => {
if (invokeMap.has(rootDirName)) {
callbacks.forEach(({ callback, dirName }) => callback(dirName));
}
});
const elapsed = timestamp() - start;
sysLog(`sysLog:: Elapsed ${elapsed}ms:: onTimerToUpdateChildWatches:: ${cacheToUpdateChildWatches.size} ${timerToUpdateChildWatches}`);
}
function removeChildWatches(parentWatcher: HostDirectoryWatcher | undefined) {
if (!parentWatcher) return;
const existingChildWatches = parentWatcher.childWatches;
parentWatcher.childWatches = emptyArray;
for (const childWatcher of existingChildWatches) {
childWatcher.close();
removeChildWatches(cache.get(toCanonicalFilePath(childWatcher.dirName)));
}
}
function updateChildWatches(dirName: string, dirPath: Path, options: WatchOptions | undefined) {
// Iterate through existing children and update the watches if needed
const parentWatcher = cache.get(dirPath);
if (parentWatcher) {
parentWatcher.childWatches = watchChildDirectories(dirName, parentWatcher.childWatches);
parentWatcher.childWatches = watchChildDirectories(dirName, parentWatcher.childWatches, options);
}
}
/**
* Watch the directories in the parentDir
*/
function watchChildDirectories(parentDir: string, existingChildWatches: ChildWatches): ChildWatches {
function watchChildDirectories(parentDir: string, existingChildWatches: ChildWatches, options: WatchOptions | undefined): ChildWatches {
let newChildWatches: ChildDirectoryWatcher[] | undefined;
enumerateInsertsAndDeletes<string, ChildDirectoryWatcher>(
host.directoryExists(parentDir) ? mapDefined(host.getAccessibleSortedChildDirectories(parentDir), child => {
@ -504,7 +656,7 @@ namespace ts {
* Create new childDirectoryWatcher and add it to the new ChildDirectoryWatcher list
*/
function createAndAddChildDirectoryWatcher(childName: string) {
const result = createDirectoryWatcher(childName);
const result = createDirectoryWatcher(childName, options);
addChildDirectoryWatcher(result);
}
@ -527,6 +679,252 @@ namespace ts {
}
}
/*@internal*/
export type FsWatchCallback = (eventName: "rename" | "change", relativeFileName: string | undefined) => void;
/*@internal*/
export type FsWatch = (fileOrDirectory: string, entryKind: FileSystemEntryKind, callback: FsWatchCallback, recursive: boolean, fallbackPollingInterval: PollingInterval, fallbackOptions: WatchOptions | undefined) => FileWatcher;
/*@internal*/
export const enum FileSystemEntryKind {
File,
Directory,
}
/*@internal*/
export function createFileWatcherCallback(callback: FsWatchCallback): FileWatcherCallback {
return (_fileName, eventKind) => callback(eventKind === FileWatcherEventKind.Changed ? "change" : "rename", "");
}
function createFsWatchCallbackForFileWatcherCallback(
fileName: string,
callback: FileWatcherCallback,
fileExists: System["fileExists"]
): FsWatchCallback {
return eventName => {
if (eventName === "rename") {
callback(fileName, fileExists(fileName) ? FileWatcherEventKind.Created : FileWatcherEventKind.Deleted);
}
else {
// Change
callback(fileName, FileWatcherEventKind.Changed);
}
};
}
function createFsWatchCallbackForDirectoryWatcherCallback(directoryName: string, callback: DirectoryWatcherCallback): FsWatchCallback {
return (eventName, relativeFileName) => {
// In watchDirectory we only care about adding and removing files (when event name is
// "rename"); changes made within files are handled by corresponding fileWatchers (when
// event name is "change")
if (eventName === "rename") {
// When deleting a file, the passed baseFileName is null
callback(!relativeFileName ? directoryName : normalizePath(combinePaths(directoryName, relativeFileName)));
}
};
}
/*@internal*/
export interface CreateSystemWatchFunctions {
// Polling watch file
pollingWatchFile: HostWatchFile;
// For dynamic polling watch file
getModifiedTime: NonNullable<System["getModifiedTime"]>;
setTimeout: NonNullable<System["setTimeout"]>;
clearTimeout: NonNullable<System["clearTimeout"]>;
// For fs events :
fsWatch: FsWatch;
fileExists: System["fileExists"];
useCaseSensitiveFileNames: boolean;
fsSupportsRecursiveFsWatch: boolean;
directoryExists: System["directoryExists"];
getAccessibleSortedChildDirectories(path: string): readonly string[];
realpath(s: string): string;
// For backward compatibility environment variables
tscWatchFile: string | undefined;
useNonPollingWatchers?: boolean;
tscWatchDirectory: string | undefined;
}
/*@internal*/
export function createSystemWatchFunctions({
pollingWatchFile,
getModifiedTime,
setTimeout,
clearTimeout,
fsWatch,
fileExists,
useCaseSensitiveFileNames,
fsSupportsRecursiveFsWatch,
directoryExists,
getAccessibleSortedChildDirectories,
realpath,
tscWatchFile,
useNonPollingWatchers,
tscWatchDirectory,
}: CreateSystemWatchFunctions): { watchFile: HostWatchFile; watchDirectory: HostWatchDirectory; } {
let dynamicPollingWatchFile: HostWatchFile | undefined;
let nonPollingWatchFile: HostWatchFile | undefined;
let hostRecursiveDirectoryWatcher: HostWatchDirectory | undefined;
return {
watchFile,
watchDirectory
};
function watchFile(fileName: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined): FileWatcher {
options = updateOptionsForWatchFile(options, useNonPollingWatchers);
const watchFileKind = Debug.assertDefined(options.watchFile);
switch (watchFileKind) {
case WatchFileKind.FixedPollingInterval:
return pollingWatchFile(fileName, callback, PollingInterval.Low, /*options*/ undefined);
case WatchFileKind.PriorityPollingInterval:
return pollingWatchFile(fileName, callback, pollingInterval, /*options*/ undefined);
case WatchFileKind.DynamicPriorityPolling:
return ensureDynamicPollingWatchFile()(fileName, callback, pollingInterval, /*options*/ undefined);
case WatchFileKind.UseFsEvents:
return fsWatch(
fileName,
FileSystemEntryKind.File,
createFsWatchCallbackForFileWatcherCallback(fileName, callback, fileExists),
/*recursive*/ false,
pollingInterval,
getFallbackOptions(options)
);
case WatchFileKind.UseFsEventsOnParentDirectory:
if (!nonPollingWatchFile) {
nonPollingWatchFile = createUseFsEventsOnParentDirectoryWatchFile(fsWatch, useCaseSensitiveFileNames);
}
return nonPollingWatchFile(fileName, callback, pollingInterval, getFallbackOptions(options));
default:
Debug.assertNever(watchFileKind);
}
}
function ensureDynamicPollingWatchFile() {
return dynamicPollingWatchFile ||
(dynamicPollingWatchFile = createDynamicPriorityPollingWatchFile({ getModifiedTime, setTimeout }));
}
function updateOptionsForWatchFile(options: WatchOptions | undefined, useNonPollingWatchers?: boolean): WatchOptions {
if (options && options.watchFile !== undefined) return options;
switch (tscWatchFile) {
case "PriorityPollingInterval":
// Use polling interval based on priority when create watch using host.watchFile
return { watchFile: WatchFileKind.PriorityPollingInterval };
case "DynamicPriorityPolling":
// Use polling interval but change the interval depending on file changes and their default polling interval
return { watchFile: WatchFileKind.DynamicPriorityPolling };
case "UseFsEvents":
// Use notifications from FS to watch with falling back to fs.watchFile
return generateWatchFileOptions(WatchFileKind.UseFsEvents, PollingWatchKind.PriorityInterval, options);
case "UseFsEventsWithFallbackDynamicPolling":
// Use notifications from FS to watch with falling back to dynamic watch file
return generateWatchFileOptions(WatchFileKind.UseFsEvents, PollingWatchKind.DynamicPriority, options);
case "UseFsEventsOnParentDirectory":
useNonPollingWatchers = true;
// fall through
default:
return useNonPollingWatchers ?
// 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: WatchFileKind.FixedPollingInterval };
}
}
function generateWatchFileOptions(
watchFile: WatchFileKind,
fallbackPolling: PollingWatchKind,
options: WatchOptions | undefined
): WatchOptions {
const defaultFallbackPolling = options?.fallbackPolling;
return {
watchFile,
fallbackPolling: defaultFallbackPolling === undefined ?
fallbackPolling :
defaultFallbackPolling
};
}
function watchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher {
if (fsSupportsRecursiveFsWatch) {
return fsWatch(
directoryName,
FileSystemEntryKind.Directory,
createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback),
recursive,
PollingInterval.Medium,
getFallbackOptions(options)
);
}
if (!hostRecursiveDirectoryWatcher) {
hostRecursiveDirectoryWatcher = createDirectoryWatcherSupportingRecursive({
useCaseSensitiveFileNames,
directoryExists,
getAccessibleSortedChildDirectories,
watchDirectory: nonRecursiveWatchDirectory,
realpath,
setTimeout,
clearTimeout
});
}
return hostRecursiveDirectoryWatcher(directoryName, callback, recursive, options);
}
function nonRecursiveWatchDirectory(directoryName: string, callback: DirectoryWatcherCallback, recursive: boolean, options: WatchOptions | undefined): FileWatcher {
Debug.assert(!recursive);
options = updateOptionsForWatchDirectory(options);
const watchDirectoryKind = Debug.assertDefined(options.watchDirectory);
switch (watchDirectoryKind) {
case WatchDirectoryKind.FixedPollingInterval:
return pollingWatchFile(
directoryName,
() => callback(directoryName),
PollingInterval.Medium,
/*options*/ undefined
);
case WatchDirectoryKind.DynamicPriorityPolling:
return ensureDynamicPollingWatchFile()(
directoryName,
() => callback(directoryName),
PollingInterval.Medium,
/*options*/ undefined
);
case WatchDirectoryKind.UseFsEvents:
return fsWatch(
directoryName,
FileSystemEntryKind.Directory,
createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback),
recursive,
PollingInterval.Medium,
getFallbackOptions(options)
);
default:
Debug.assertNever(watchDirectoryKind);
}
}
function updateOptionsForWatchDirectory(options: WatchOptions | undefined): WatchOptions {
if (options && options.watchDirectory !== undefined) return options;
switch (tscWatchDirectory) {
case "RecursiveDirectoryUsingFsWatchFile":
// Use polling interval based on priority when create watch using host.watchFile
return { watchDirectory: WatchDirectoryKind.FixedPollingInterval };
case "RecursiveDirectoryUsingDynamicPriorityPolling":
// Use polling interval but change the interval depending on file changes and their default polling interval
return { watchDirectory: WatchDirectoryKind.DynamicPriorityPolling };
default:
const defaultFallbackPolling = options?.fallbackPolling;
return {
watchDirectory: WatchDirectoryKind.UseFsEvents,
fallbackPolling: defaultFallbackPolling !== undefined ?
defaultFallbackPolling :
undefined
};
}
}
}
/**
* patch writefile to create folder before writing the file
*/
@ -641,8 +1039,8 @@ namespace ts {
* @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that
* use native OS file watching
*/
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher;
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher;
resolvePath(path: string): string;
fileExists(path: string): boolean;
directoryExists(path: string): boolean;
@ -769,17 +1167,25 @@ namespace ts {
const platform: string = _os.platform();
const useCaseSensitiveFileNames = isFileSystemCaseSensitive();
const enum FileSystemEntryKind {
File,
Directory
}
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 fsSupportsRecursiveFsWatch = isNode4OrLater && (process.platform === "win32" || process.platform === "darwin");
const { watchFile, watchDirectory } = createSystemWatchFunctions({
pollingWatchFile: createSingleFileWatcherPerName(fsWatchFileWorker, useCaseSensitiveFileNames),
getModifiedTime,
setTimeout,
clearTimeout,
fsWatch,
useCaseSensitiveFileNames,
fileExists,
// Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows
// (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643)
fsSupportsRecursiveFsWatch,
directoryExists,
getAccessibleSortedChildDirectories: path => getAccessibleFileSystemEntries(path).directories,
realpath,
tscWatchFile: process.env.TSC_WATCHFILE,
useNonPollingWatchers: process.env.TSC_NONPOLLING_WATCHER,
tscWatchDirectory: process.env.TSC_WATCHDIRECTORY,
});
const nodeSystem: System = {
args: process.argv.slice(2),
newLine: _os.EOL,
@ -792,8 +1198,8 @@ namespace ts {
},
readFile,
writeFile,
watchFile: getWatchFile(),
watchDirectory: getWatchDirectory(),
watchFile,
watchDirectory,
resolvePath: path => _path.resolve(path),
fileExists,
directoryExists,
@ -994,112 +1400,8 @@ namespace ts {
});
}
function getWatchFile(): HostWatchFile {
switch (tscWatchFile) {
case "PriorityPollingInterval":
// Use polling interval based on priority when create watch using host.watchFile
return fsWatchFile;
case "DynamicPriorityPolling":
// Use polling interval but change the interval depending on file changes and their default polling interval
return createDynamicPriorityPollingWatchFile({ getModifiedTime, setTimeout });
case "UseFsEvents":
// Use notifications from FS to watch with falling back to fs.watchFile
return watchFileUsingFsWatch;
case "UseFsEventsWithFallbackDynamicPolling":
// Use notifications from FS to watch with falling back to dynamic watch file
dynamicPollingWatchFile = createDynamicPriorityPollingWatchFile({ getModifiedTime, setTimeout });
return createWatchFileUsingDynamicWatchFile(dynamicPollingWatchFile);
case "UseFsEventsOnParentDirectory":
// Use notifications from FS to watch with falling back to fs.watchFile
return createNonPollingWatchFile();
}
return useNonPollingWatchers ?
createNonPollingWatchFile() :
// Default to do not use polling interval as it is before this experiment branch
(fileName, callback) => fsWatchFile(fileName, callback, /*pollingInterval*/ undefined);
}
function getWatchDirectory(): HostWatchDirectory {
// Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows
// (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643)
const fsSupportsRecursive = isNode4OrLater && (process.platform === "win32" || process.platform === "darwin");
if (fsSupportsRecursive) {
return watchDirectoryUsingFsWatch;
}
// defer watchDirectoryRecursively as it depends on `ts.createMap()` which may not be usable yet.
const watchDirectory = tscWatchDirectory === "RecursiveDirectoryUsingFsWatchFile" ?
createWatchDirectoryUsing(fsWatchFile) :
tscWatchDirectory === "RecursiveDirectoryUsingDynamicPriorityPolling" ?
createWatchDirectoryUsing(dynamicPollingWatchFile || createDynamicPriorityPollingWatchFile({ getModifiedTime, setTimeout })) :
watchDirectoryUsingFsWatch;
const watchDirectoryRecursively = createRecursiveDirectoryWatcher({
useCaseSensitiveFileNames,
directoryExists,
getAccessibleSortedChildDirectories: path => getAccessibleFileSystemEntries(path).directories,
watchDirectory,
realpath
});
return (directoryName, callback, recursive) => {
if (recursive) {
return watchDirectoryRecursively(directoryName, callback);
}
return watchDirectory(directoryName, callback);
};
}
function createNonPollingWatchFile() {
// One file can have multiple watchers
const fileWatcherCallbacks = createMultiMap<FileWatcherCallback>();
const dirWatchers = createMap<DirectoryWatcher>();
const toCanonicalName = createGetCanonicalFileName(useCaseSensitiveFileNames);
return nonPollingWatchFile;
function nonPollingWatchFile(fileName: string, callback: FileWatcherCallback): FileWatcher {
const filePath = toCanonicalName(fileName);
fileWatcherCallbacks.add(filePath, callback);
const dirPath = getDirectoryPath(filePath) || ".";
const watcher = dirWatchers.get(dirPath) || createDirectoryWatcher(getDirectoryPath(fileName) || ".", dirPath);
watcher.referenceCount++;
return {
close: () => {
if (watcher.referenceCount === 1) {
watcher.close();
dirWatchers.delete(dirPath);
}
else {
watcher.referenceCount--;
}
fileWatcherCallbacks.remove(filePath, callback);
}
};
}
function createDirectoryWatcher(dirName: string, dirPath: string) {
const watcher = fsWatchDirectory(
dirName,
(_eventName: string, relativeFileName) => {
// When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined"
if (!isString(relativeFileName)) { return; }
const fileName = getNormalizedAbsolutePath(relativeFileName, dirName);
// Some applications save a working file via rename operations
const callbacks = fileName && fileWatcherCallbacks.get(toCanonicalName(fileName));
if (callbacks) {
for (const fileCallback of callbacks) {
fileCallback(fileName, FileWatcherEventKind.Changed);
}
}
}
) as DirectoryWatcher;
watcher.referenceCount = 0;
dirWatchers.set(dirPath, watcher);
return watcher;
}
}
function fsWatchFileWorker(fileName: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher {
_fs.watchFile(fileName, { persistent: true, interval: pollingInterval || 250 }, fileChanged);
function fsWatchFileWorker(fileName: string, callback: FileWatcherCallback, pollingInterval: number): FileWatcher {
_fs.watchFile(fileName, { persistent: true, interval: pollingInterval }, fileChanged);
let eventKind: FileWatcherEventKind;
return {
close: () => _fs.unwatchFile(fileName, fileChanged)
@ -1131,37 +1433,14 @@ namespace ts {
}
}
type FsWatchCallback = (eventName: "rename" | "change", relativeFileName: string | undefined) => void;
function createFileWatcherCallback(callback: FsWatchCallback): FileWatcherCallback {
return (_fileName, eventKind) => callback(eventKind === FileWatcherEventKind.Changed ? "change" : "rename", "");
}
function createFsWatchCallbackForFileWatcherCallback(fileName: string, callback: FileWatcherCallback): FsWatchCallback {
return eventName => {
if (eventName === "rename") {
callback(fileName, fileExists(fileName) ? FileWatcherEventKind.Created : FileWatcherEventKind.Deleted);
}
else {
// Change
callback(fileName, FileWatcherEventKind.Changed);
}
};
}
function createFsWatchCallbackForDirectoryWatcherCallback(directoryName: string, callback: DirectoryWatcherCallback): FsWatchCallback {
return (eventName, relativeFileName) => {
// In watchDirectory we only care about adding and removing files (when event name is
// "rename"); changes made within files are handled by corresponding fileWatchers (when
// event name is "change")
if (eventName === "rename") {
// When deleting a file, the passed baseFileName is null
callback(!relativeFileName ? directoryName : normalizePath(combinePaths(directoryName, relativeFileName)));
}
};
}
function fsWatch(fileOrDirectory: string, entryKind: FileSystemEntryKind.File | FileSystemEntryKind.Directory, callback: FsWatchCallback, recursive: boolean, fallbackPollingWatchFile: HostWatchFile, pollingInterval?: number): FileWatcher {
function fsWatch(
fileOrDirectory: string,
entryKind: FileSystemEntryKind,
callback: FsWatchCallback,
recursive: boolean,
fallbackPollingInterval: PollingInterval,
fallbackOptions: WatchOptions | undefined
): FileWatcher {
let options: any;
let lastDirectoryPartWithDirectorySeparator: string | undefined;
let lastDirectoryPart: string | undefined;
@ -1205,7 +1484,7 @@ namespace ts {
// Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows
// (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643)
if (options === undefined) {
if (isNode4OrLater && (process.platform === "win32" || process.platform === "darwin")) {
if (fsSupportsRecursiveFsWatch) {
options = { persistent: true, recursive: !!recursive };
}
else {
@ -1250,7 +1529,12 @@ namespace ts {
*/
function watchPresentFileSystemEntryWithFsWatchFile(): FileWatcher {
sysLog(`sysLog:: ${fileOrDirectory}:: Changing to fsWatchFile`);
return fallbackPollingWatchFile(fileOrDirectory, createFileWatcherCallback(callback), pollingInterval);
return watchFile(
fileOrDirectory,
createFileWatcherCallback(callback),
fallbackPollingInterval,
fallbackOptions
);
}
/**
@ -1258,37 +1542,22 @@ namespace ts {
* and switch to existing file or directory when the missing filesystem entry is created
*/
function watchMissingFileSystemEntry(): FileWatcher {
return fallbackPollingWatchFile(fileOrDirectory, (_fileName, eventKind) => {
if (eventKind === FileWatcherEventKind.Created && fileSystemEntryExists(fileOrDirectory, entryKind)) {
// Call the callback for current file or directory
// For now it could be callback for the inner directory creation,
// but just return current directory, better than current no-op
invokeCallbackAndUpdateWatcher(watchPresentFileSystemEntry);
}
}, pollingInterval);
return watchFile(
fileOrDirectory,
(_fileName, eventKind) => {
if (eventKind === FileWatcherEventKind.Created && fileSystemEntryExists(fileOrDirectory, entryKind)) {
// Call the callback for current file or directory
// For now it could be callback for the inner directory creation,
// but just return current directory, better than current no-op
invokeCallbackAndUpdateWatcher(watchPresentFileSystemEntry);
}
},
fallbackPollingInterval,
fallbackOptions
);
}
}
function watchFileUsingFsWatch(fileName: string, callback: FileWatcherCallback, pollingInterval?: number) {
return fsWatch(fileName, FileSystemEntryKind.File, createFsWatchCallbackForFileWatcherCallback(fileName, callback), /*recursive*/ false, fsWatchFile, pollingInterval);
}
function createWatchFileUsingDynamicWatchFile(watchFile: HostWatchFile): HostWatchFile {
return (fileName, callback, pollingInterval) => fsWatch(fileName, FileSystemEntryKind.File, createFsWatchCallbackForFileWatcherCallback(fileName, callback), /*recursive*/ false, watchFile, pollingInterval);
}
function fsWatchDirectory(directoryName: string, callback: FsWatchCallback, recursive?: boolean): FileWatcher {
return fsWatch(directoryName, FileSystemEntryKind.Directory, callback, !!recursive, fsWatchFile);
}
function watchDirectoryUsingFsWatch(directoryName: string, callback: DirectoryWatcherCallback, recursive?: boolean) {
return fsWatchDirectory(directoryName, createFsWatchCallbackForDirectoryWatcherCallback(directoryName, callback), recursive);
}
function createWatchDirectoryUsing(fsWatchFile: HostWatchFile): HostWatchDirectory {
return (directoryName, callback) => fsWatchFile(directoryName, () => callback(directoryName), PollingInterval.Medium);
}
function readFileWorker(fileName: string, _encoding?: string): string | undefined {
if (!fileExists(fileName)) {
return undefined;

View file

@ -205,8 +205,8 @@ namespace ts {
return createSolutionBuilderWorker(/*watch*/ false, host, rootNames, defaultOptions);
}
export function createSolutionBuilderWithWatch<T extends BuilderProgram>(host: SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder<T> {
return createSolutionBuilderWorker(/*watch*/ true, host, rootNames, defaultOptions);
export function createSolutionBuilderWithWatch<T extends BuilderProgram>(host: SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions, baseWatchOptions?: WatchOptions): SolutionBuilder<T> {
return createSolutionBuilderWorker(/*watch*/ true, host, rootNames, defaultOptions, baseWatchOptions);
}
type ConfigFileCacheEntry = ParsedCommandLine | Diagnostic;
@ -232,6 +232,7 @@ namespace ts {
readonly options: BuildOptions;
readonly baseCompilerOptions: CompilerOptions;
readonly rootNames: readonly string[];
readonly baseWatchOptions: WatchOptions | undefined;
readonly resolvedConfigFilePaths: Map<ResolvedConfigFilePath>;
readonly configFileCache: ConfigFileMap<ConfigFileCacheEntry>;
@ -272,7 +273,7 @@ namespace ts {
writeLog: (s: string) => void;
}
function createSolutionBuilderState<T extends BuilderProgram>(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], options: BuildOptions): SolutionBuilderState<T> {
function createSolutionBuilderState<T extends BuilderProgram>(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], options: BuildOptions, baseWatchOptions: WatchOptions | undefined): SolutionBuilderState<T> {
const host = hostOrHostWithWatch as SolutionBuilderHost<T>;
const hostWithWatch = hostOrHostWithWatch as SolutionBuilderWithWatchHost<T>;
const currentDirectory = host.getCurrentDirectory();
@ -306,6 +307,7 @@ namespace ts {
options,
baseCompilerOptions,
rootNames,
baseWatchOptions,
resolvedConfigFilePaths: createMap(),
configFileCache: createConfigFileMap(),
@ -374,7 +376,7 @@ namespace ts {
}
let diagnostic: Diagnostic | undefined;
const { parseConfigFileHost, baseCompilerOptions, extendedConfigCache, host } = state;
const { parseConfigFileHost, baseCompilerOptions, baseWatchOptions, extendedConfigCache, host } = state;
let parsed: ParsedCommandLine | undefined;
if (host.getParsedCommandLine) {
parsed = host.getParsedCommandLine(configFileName);
@ -382,7 +384,7 @@ namespace ts {
}
else {
parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = d => diagnostic = d;
parsed = getParsedCommandLineOfConfigFile(configFileName, baseCompilerOptions, parseConfigFileHost, extendedConfigCache);
parsed = getParsedCommandLineOfConfigFile(configFileName, baseCompilerOptions, parseConfigFileHost, extendedConfigCache, baseWatchOptions);
parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = noop;
}
configFileCache.set(configFilePath, parsed || diagnostic!);
@ -1147,7 +1149,7 @@ namespace ts {
}
if (reloadLevel === ConfigFileProgramReloadLevel.Full) {
watchConfigFile(state, project, projectPath);
watchConfigFile(state, project, projectPath, config);
watchWildCardDirectories(state, project, projectPath, config);
watchInputFiles(state, project, projectPath, config);
}
@ -1751,7 +1753,7 @@ namespace ts {
reportErrorSummary(state, buildOrder);
}
function watchConfigFile(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath) {
function watchConfigFile(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine | undefined) {
if (!state.watch || state.allWatchedConfigFiles.has(resolvedPath)) return;
state.allWatchedConfigFiles.set(resolvedPath, state.watchFile(
state.hostWithWatch,
@ -1760,6 +1762,7 @@ namespace ts {
invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.Full);
},
PollingInterval.High,
parsed?.watchOptions,
WatchType.ConfigFile,
resolved
));
@ -1820,6 +1823,7 @@ namespace ts {
invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.Partial);
},
flags,
parsed?.watchOptions,
WatchType.WildcardDirectory,
resolved
)
@ -1837,6 +1841,7 @@ namespace ts {
input,
() => invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.None),
PollingInterval.Low,
parsed?.watchOptions,
path as Path,
WatchType.SourceFile,
resolved
@ -1851,10 +1856,9 @@ namespace ts {
state.watchAllProjectsPending = false;
for (const resolved of getBuildOrderFromAnyBuildOrder(buildOrder)) {
const resolvedPath = toResolvedConfigFilePath(state, resolved);
// Watch this file
watchConfigFile(state, resolved, resolvedPath);
const cfg = parseConfigFile(state, resolved, resolvedPath);
// Watch this file
watchConfigFile(state, resolved, resolvedPath, cfg);
if (cfg) {
// Update watchers for wildcard directories
watchWildCardDirectories(state, resolved, resolvedPath, cfg);
@ -1870,9 +1874,9 @@ namespace ts {
* can dynamically add/remove other projects based on changes on the rootNames' references
*/
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: false, host: SolutionBuilderHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder<T>;
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: true, host: SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder<T>;
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], options: BuildOptions): SolutionBuilder<T> {
const state = createSolutionBuilderState(watch, hostOrHostWithWatch, rootNames, options);
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: true, host: SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions, baseWatchOptions?: WatchOptions): SolutionBuilder<T>;
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], options: BuildOptions, baseWatchOptions?: WatchOptions): SolutionBuilder<T> {
const state = createSolutionBuilderState(watch, hostOrHostWithWatch, rootNames, options, baseWatchOptions);
return {
build: (project, cancellationToken) => build(state, project, cancellationToken),
clean: project => clean(state, project),

View file

@ -4929,6 +4929,26 @@ namespace ts {
circular?: boolean;
}
export enum WatchFileKind {
FixedPollingInterval,
PriorityPollingInterval,
DynamicPriorityPolling,
UseFsEvents,
UseFsEventsOnParentDirectory,
}
export enum WatchDirectoryKind {
UseFsEvents,
FixedPollingInterval,
DynamicPriorityPolling,
}
export enum PollingWatchKind {
FixedInterval,
PriorityInterval,
DynamicPriority,
}
export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[] | ProjectReference[] | null | undefined;
export interface CompilerOptions {
@ -5043,6 +5063,15 @@ namespace ts {
[option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined;
}
export interface WatchOptions {
watchFile?: WatchFileKind;
watchDirectory?: WatchDirectoryKind;
fallbackPolling?: PollingWatchKind;
synchronousWatchDirectory?: boolean;
[option: string]: CompilerOptionsValue | undefined;
}
export interface TypeAcquisition {
/**
* @deprecated typingOptions.enableAutoDiscovery
@ -5130,6 +5159,7 @@ namespace ts {
typeAcquisition?: TypeAcquisition;
fileNames: string[];
projectReferences?: readonly ProjectReference[];
watchOptions?: WatchOptions;
raw?: any;
errors: Diagnostic[];
wildcardDirectories?: MapLike<WatchDirectoryFlags>;
@ -5210,7 +5240,8 @@ namespace ts {
}
/* @internal */
export interface DidYouMeanOptionalDiagnostics {
export interface DidYouMeanOptionsDiagnostics {
optionDeclarations: CommandLineOption[];
unknownOptionDiagnostic: DiagnosticMessage,
unknownDidYouMeanDiagnostic: DiagnosticMessage,
}
@ -5219,7 +5250,7 @@ namespace ts {
export interface TsConfigOnlyOption extends CommandLineOptionBase {
type: "object";
elementOptions?: Map<CommandLineOption>;
extraKeyDiagnostics?: DidYouMeanOptionalDiagnostics;
extraKeyDiagnostics?: DidYouMeanOptionsDiagnostics;
}
/* @internal */

View file

@ -89,10 +89,10 @@ namespace ts {
}
/** Parses config file using System interface */
export function parseConfigFileWithSystem(configFileName: string, optionsToExtend: CompilerOptions, system: System, reportDiagnostic: DiagnosticReporter) {
export function parseConfigFileWithSystem(configFileName: string, optionsToExtend: CompilerOptions, watchOptionsToExtend: WatchOptions | undefined, system: System, reportDiagnostic: DiagnosticReporter) {
const host: ParseConfigFileHost = <any>system;
host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(system, reportDiagnostic, diagnostic);
const result = getParsedCommandLineOfConfigFile(configFileName, optionsToExtend, host);
const result = getParsedCommandLineOfConfigFile(configFileName, optionsToExtend, host, /*extendedConfigCache*/ undefined, watchOptionsToExtend);
host.onUnRecoverableConfigFileDiagnostic = undefined!; // TODO: GH#18217
return result;
}
@ -419,22 +419,24 @@ namespace ts {
/**
* Creates the watch compiler host from system for config file in watch mode
*/
export function createWatchCompilerHostOfConfigFile<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHostOfConfigFile<T> {
export function createWatchCompilerHostOfConfigFile<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(configFileName: string, optionsToExtend: CompilerOptions | undefined, watchOptionsToExtend: WatchOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHostOfConfigFile<T> {
const diagnosticReporter = reportDiagnostic || createDiagnosticReporter(system);
const host = createWatchCompilerHost(system, createProgram, diagnosticReporter, reportWatchStatus) as WatchCompilerHostOfConfigFile<T>;
host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(system, diagnosticReporter, diagnostic);
host.configFileName = configFileName;
host.optionsToExtend = optionsToExtend;
host.watchOptionsToExtend = watchOptionsToExtend;
return host;
}
/**
* Creates the watch compiler host from system for compiling root files and options in watch mode
*/
export function createWatchCompilerHostOfFilesAndCompilerOptions<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(rootFiles: string[], options: CompilerOptions, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: readonly ProjectReference[]): WatchCompilerHostOfFilesAndCompilerOptions<T> {
export function createWatchCompilerHostOfFilesAndCompilerOptions<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(rootFiles: string[], options: CompilerOptions, watchOptions: WatchOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: readonly ProjectReference[]): WatchCompilerHostOfFilesAndCompilerOptions<T> {
const host = createWatchCompilerHost(system, createProgram, reportDiagnostic || createDiagnosticReporter(system), reportWatchStatus) as WatchCompilerHostOfFilesAndCompilerOptions<T>;
host.rootFiles = rootFiles;
host.options = options;
host.watchOptions = watchOptions;
host.projectReferences = projectReferences;
return host;
}

View file

@ -52,9 +52,9 @@ namespace ts {
onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions, errorCount?: number): void;
/** Used to watch changes in source files, missing files needed to update the program or config file */
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: CompilerOptions): FileWatcher;
/** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: CompilerOptions): FileWatcher;
/** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */
setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
/** If provided, will be used to reset existing delayed compilation */
@ -133,6 +133,8 @@ namespace ts {
/** Compiler options */
options: CompilerOptions;
watchOptions?: WatchOptions;
/** Project References */
projectReferences?: readonly ProjectReference[];
}
@ -147,6 +149,8 @@ namespace ts {
/** Options to extend */
optionsToExtend?: CompilerOptions;
watchOptionsToExtend?: WatchOptions;
/**
* Used to generate source file names from the config file and its include, exclude, files rules
* and also to cache the directory stucture
@ -159,7 +163,6 @@ namespace ts {
*/
/*@internal*/
export interface WatchCompilerHostOfConfigFile<T extends BuilderProgram> extends WatchCompilerHost<T> {
optionsToExtend?: CompilerOptions;
configFileParsingResult?: ParsedCommandLine;
}
@ -190,14 +193,14 @@ namespace ts {
/**
* Create the watch compiler host for either configFile or fileNames and its options
*/
export function createWatchCompilerHost<T extends BuilderProgram>(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHostOfConfigFile<T>;
export function createWatchCompilerHost<T extends BuilderProgram>(rootFiles: string[], options: CompilerOptions, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: readonly ProjectReference[]): WatchCompilerHostOfFilesAndCompilerOptions<T>;
export function createWatchCompilerHost<T extends BuilderProgram>(rootFilesOrConfigFileName: string | string[], options: CompilerOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: readonly ProjectReference[]): WatchCompilerHostOfFilesAndCompilerOptions<T> | WatchCompilerHostOfConfigFile<T> {
export function createWatchCompilerHost<T extends BuilderProgram>(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, watchOptionsToExtend?: WatchOptions): WatchCompilerHostOfConfigFile<T>;
export function createWatchCompilerHost<T extends BuilderProgram>(rootFiles: string[], options: CompilerOptions, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: readonly ProjectReference[], watchOptions?: WatchOptions): WatchCompilerHostOfFilesAndCompilerOptions<T>;
export function createWatchCompilerHost<T extends BuilderProgram>(rootFilesOrConfigFileName: string | string[], options: CompilerOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferencesOrWatchOptionsToExtend?: readonly ProjectReference[] | WatchOptions, watchOptions?: WatchOptions): WatchCompilerHostOfFilesAndCompilerOptions<T> | WatchCompilerHostOfConfigFile<T> {
if (isArray(rootFilesOrConfigFileName)) {
return createWatchCompilerHostOfFilesAndCompilerOptions(rootFilesOrConfigFileName, options!, system, createProgram, reportDiagnostic, reportWatchStatus, projectReferences); // TODO: GH#18217
return createWatchCompilerHostOfFilesAndCompilerOptions(rootFilesOrConfigFileName, options!, watchOptions, system, createProgram, reportDiagnostic, reportWatchStatus, projectReferencesOrWatchOptionsToExtend as readonly ProjectReference[]); // TODO: GH#18217
}
else {
return createWatchCompilerHostOfConfigFile(rootFilesOrConfigFileName, options, system, createProgram, reportDiagnostic, reportWatchStatus);
return createWatchCompilerHostOfConfigFile(rootFilesOrConfigFileName, options, projectReferencesOrWatchOptionsToExtend as WatchOptions, system, createProgram, reportDiagnostic, reportWatchStatus);
}
}
@ -236,8 +239,8 @@ namespace ts {
const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames();
const currentDirectory = host.getCurrentDirectory();
const { configFileName, optionsToExtend: optionsToExtendForConfigFile = {}, createProgram } = host;
let { rootFiles: rootFileNames, options: compilerOptions, projectReferences } = host;
const { configFileName, optionsToExtend: optionsToExtendForConfigFile = {}, watchOptionsToExtend, createProgram } = host;
let { rootFiles: rootFileNames, options: compilerOptions, watchOptions, projectReferences } = host;
let configFileSpecs: ConfigFileSpecs;
let configFileParsingDiagnostics: Diagnostic[] | undefined;
let canConfigFileJsonReportNoInputFiles = false;
@ -270,7 +273,7 @@ namespace ts {
writeLog(`Current directory: ${currentDirectory} CaseSensitiveFileNames: ${useCaseSensitiveFileNames}`);
let configFileWatcher: FileWatcher | undefined;
if (configFileName) {
configFileWatcher = watchFile(host, configFileName, scheduleProgramReload, PollingInterval.High, WatchType.ConfigFile);
configFileWatcher = watchFile(host, configFileName, scheduleProgramReload, PollingInterval.High, watchOptions, WatchType.ConfigFile);
}
const compilerHost = createCompilerHostFromProgramHost(host, () => compilerOptions, directoryStructureHost) as CompilerHost & ResolutionCacheHost;
@ -285,8 +288,8 @@ namespace ts {
// Members for ResolutionCacheHost
compilerHost.toPath = toPath;
compilerHost.getCompilationSettings = () => compilerOptions;
compilerHost.watchDirectoryOfFailedLookupLocation = (dir, cb, flags) => watchDirectory(host, dir, cb, flags, WatchType.FailedLookupLocations);
compilerHost.watchTypeRootsDirectory = (dir, cb, flags) => watchDirectory(host, dir, cb, flags, WatchType.TypeRoots);
compilerHost.watchDirectoryOfFailedLookupLocation = (dir, cb, flags) => watchDirectory(host, dir, cb, flags, watchOptions, WatchType.FailedLookupLocations);
compilerHost.watchTypeRootsDirectory = (dir, cb, flags) => watchDirectory(host, dir, cb, flags, watchOptions, WatchType.TypeRoots);
compilerHost.getCachedDirectoryStructureHost = () => cachedDirectoryStructureHost;
compilerHost.onInvalidatedResolution = scheduleProgramUpdate;
compilerHost.onChangedAutomaticTypeDirectiveNames = () => {
@ -469,7 +472,7 @@ namespace ts {
(hostSourceFile as FilePresentOnHost).sourceFile = sourceFile;
hostSourceFile.version = sourceFile.version;
if (!hostSourceFile.fileWatcher) {
hostSourceFile.fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path, WatchType.SourceFile);
hostSourceFile.fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, watchOptions, path, WatchType.SourceFile);
}
}
else {
@ -482,7 +485,7 @@ namespace ts {
}
else {
if (sourceFile) {
const fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, path, WatchType.SourceFile);
const fileWatcher = watchFilePath(host, fileName, onSourceFileChange, PollingInterval.Low, watchOptions, path, WatchType.SourceFile);
sourceFilesCache.set(path, { sourceFile, version: sourceFile.version, fileWatcher });
}
else {
@ -611,12 +614,13 @@ namespace ts {
}
function parseConfigFile() {
setConfigFileParsingResult(getParsedCommandLineOfConfigFile(configFileName, optionsToExtendForConfigFile, parseConfigFileHost)!); // TODO: GH#18217
setConfigFileParsingResult(getParsedCommandLineOfConfigFile(configFileName, optionsToExtendForConfigFile, parseConfigFileHost, /*extendedConfigCache*/ undefined, watchOptionsToExtend)!); // TODO: GH#18217
}
function setConfigFileParsingResult(configFileParseResult: ParsedCommandLine) {
rootFileNames = configFileParseResult.fileNames;
compilerOptions = configFileParseResult.options;
watchOptions = configFileParseResult.watchOptions;
configFileSpecs = configFileParseResult.configFileSpecs!; // TODO: GH#18217
projectReferences = configFileParseResult.projectReferences;
configFileParsingDiagnostics = getConfigFileParsingDiagnostics(configFileParseResult).slice();
@ -645,7 +649,7 @@ namespace ts {
}
function watchMissingFilePath(missingFilePath: Path) {
return watchFilePath(host, missingFilePath, onMissingFileChange, PollingInterval.Medium, missingFilePath, WatchType.MissingFile);
return watchFilePath(host, missingFilePath, onMissingFileChange, PollingInterval.Medium, watchOptions, missingFilePath, WatchType.MissingFile);
}
function onMissingFileChange(fileName: string, eventKind: FileWatcherEventKind, missingFilePath: Path) {
@ -709,6 +713,7 @@ namespace ts {
}
},
flags,
watchOptions,
WatchType.WildcardDirectory
);
}

View file

@ -344,15 +344,15 @@ namespace ts {
}
export interface WatchFileHost {
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher;
}
export interface WatchDirectoryHost {
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher;
}
export type WatchFile<X, Y> = (host: WatchFileHost, file: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, detailInfo1: X, detailInfo2?: Y) => FileWatcher;
export type WatchFile<X, Y> = (host: WatchFileHost, file: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined, detailInfo1: X, detailInfo2?: Y) => FileWatcher;
export type FilePathWatcherCallback = (fileName: string, eventKind: FileWatcherEventKind, filePath: Path) => void;
export type WatchFilePath<X, Y> = (host: WatchFileHost, file: string, callback: FilePathWatcherCallback, pollingInterval: PollingInterval, path: Path, detailInfo1: X, detailInfo2?: Y) => FileWatcher;
export type WatchDirectory<X, Y> = (host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, detailInfo1: X, detailInfo2?: Y) => FileWatcher;
export type WatchFilePath<X, Y> = (host: WatchFileHost, file: string, callback: FilePathWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined, path: Path, detailInfo1: X, detailInfo2?: Y) => FileWatcher;
export type WatchDirectory<X, Y> = (host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, options: WatchOptions | undefined, detailInfo1: X, detailInfo2?: Y) => FileWatcher;
export interface WatchFactory<X, Y> {
watchFile: WatchFile<X, Y>;
@ -364,9 +364,13 @@ namespace ts {
return getWatchFactoryWith(watchLogLevel, log, getDetailWatchInfo, watchFile, watchDirectory);
}
function getWatchFactoryWith<X, Y = undefined>(watchLogLevel: WatchLogLevel, log: (s: string) => void, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined,
watchFile: (host: WatchFileHost, file: string, callback: FileWatcherCallback, watchPriority: PollingInterval) => FileWatcher,
watchDirectory: (host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags) => FileWatcher): WatchFactory<X, Y> {
function getWatchFactoryWith<X, Y = undefined>(
watchLogLevel: WatchLogLevel,
log: (s: string) => void,
getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined,
watchFile: (host: WatchFileHost, file: string, callback: FileWatcherCallback, watchPriority: PollingInterval, options: WatchOptions | undefined) => FileWatcher,
watchDirectory: (host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, options: WatchOptions | undefined) => FileWatcher
): WatchFactory<X, Y> {
const createFileWatcher: CreateFileWatcher<WatchFileHost, PollingInterval, FileWatcherEventKind, never, X, Y> = getCreateFileWatcher(watchLogLevel, watchFile);
const createFilePathWatcher: CreateFileWatcher<WatchFileHost, PollingInterval, FileWatcherEventKind, Path, X, Y> = watchLogLevel === WatchLogLevel.None ? watchFilePath : createFileWatcher;
const createDirectoryWatcher: CreateFileWatcher<WatchDirectoryHost, WatchDirectoryFlags, undefined, never, X, Y> = getCreateFileWatcher(watchLogLevel, watchDirectory);
@ -374,32 +378,32 @@ namespace ts {
setSysLog(s => log(s));
}
return {
watchFile: (host, file, callback, pollingInterval, detailInfo1, detailInfo2) =>
createFileWatcher(host, file, callback, pollingInterval, /*passThrough*/ undefined, detailInfo1, detailInfo2, watchFile, log, "FileWatcher", getDetailWatchInfo),
watchFilePath: (host, file, callback, pollingInterval, path, detailInfo1, detailInfo2) =>
createFilePathWatcher(host, file, callback, pollingInterval, path, detailInfo1, detailInfo2, watchFile, log, "FileWatcher", getDetailWatchInfo),
watchDirectory: (host, directory, callback, flags, detailInfo1, detailInfo2) =>
createDirectoryWatcher(host, directory, callback, flags, /*passThrough*/ undefined, detailInfo1, detailInfo2, watchDirectory, log, "DirectoryWatcher", getDetailWatchInfo)
watchFile: (host, file, callback, pollingInterval, options, detailInfo1, detailInfo2) =>
createFileWatcher(host, file, callback, pollingInterval, options, /*passThrough*/ undefined, detailInfo1, detailInfo2, watchFile, log, "FileWatcher", getDetailWatchInfo),
watchFilePath: (host, file, callback, pollingInterval, options, path, detailInfo1, detailInfo2) =>
createFilePathWatcher(host, file, callback, pollingInterval, options, path, detailInfo1, detailInfo2, watchFile, log, "FileWatcher", getDetailWatchInfo),
watchDirectory: (host, directory, callback, flags, options, detailInfo1, detailInfo2) =>
createDirectoryWatcher(host, directory, callback, flags, options, /*passThrough*/ undefined, detailInfo1, detailInfo2, watchDirectory, log, "DirectoryWatcher", getDetailWatchInfo)
};
function watchFilePath(host: WatchFileHost, file: string, callback: FilePathWatcherCallback, pollingInterval: PollingInterval, path: Path): FileWatcher {
return watchFile(host, file, (fileName, eventKind) => callback(fileName, eventKind, path), pollingInterval);
}
}
function watchFile(host: WatchFileHost, file: string, callback: FileWatcherCallback, pollingInterval: PollingInterval): FileWatcher {
return host.watchFile(file, callback, pollingInterval);
function watchFile(host: WatchFileHost, file: string, callback: FileWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined): FileWatcher {
return host.watchFile(file, callback, pollingInterval, options);
}
function watchDirectory(host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags): FileWatcher {
return host.watchDirectory(directory, callback, (flags & WatchDirectoryFlags.Recursive) !== 0);
function watchFilePath(host: WatchFileHost, file: string, callback: FilePathWatcherCallback, pollingInterval: PollingInterval, options: WatchOptions | undefined, path: Path): FileWatcher {
return watchFile(host, file, (fileName, eventKind) => callback(fileName, eventKind, path), pollingInterval, options);
}
function watchDirectory(host: WatchDirectoryHost, directory: string, callback: DirectoryWatcherCallback, flags: WatchDirectoryFlags, options: WatchOptions | undefined): FileWatcher {
return host.watchDirectory(directory, callback, (flags & WatchDirectoryFlags.Recursive) !== 0, options);
}
type WatchCallback<T, U> = (fileName: string, cbOptional?: T, passThrough?: U) => void;
type AddWatch<H, T, U, V> = (host: H, file: string, cb: WatchCallback<U, V>, flags: T, passThrough?: V, detailInfo1?: undefined, detailInfo2?: undefined) => FileWatcher;
type AddWatch<H, T, U, V> = (host: H, file: string, cb: WatchCallback<U, V>, flags: T, options: WatchOptions | undefined, passThrough?: V, detailInfo1?: undefined, detailInfo2?: undefined) => FileWatcher;
export type GetDetailWatchInfo<X, Y> = (detailInfo1: X, detailInfo2: Y | undefined) => string;
type CreateFileWatcher<H, T, U, V, X, Y> = (host: H, file: string, cb: WatchCallback<U, V>, flags: T, passThrough: V | undefined, detailInfo1: X | undefined, detailInfo2: Y | undefined, addWatch: AddWatch<H, T, U, V>, log: (s: string) => void, watchCaption: string, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined) => FileWatcher;
type CreateFileWatcher<H, T, U, V, X, Y> = (host: H, file: string, cb: WatchCallback<U, V>, flags: T, options: WatchOptions | undefined, passThrough: V | undefined, detailInfo1: X | undefined, detailInfo2: Y | undefined, addWatch: AddWatch<H, T, U, V>, log: (s: string) => void, watchCaption: string, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined) => FileWatcher;
function getCreateFileWatcher<H, T, U, V, X, Y>(watchLogLevel: WatchLogLevel, addWatch: AddWatch<H, T, U, V>): CreateFileWatcher<H, T, U, V, X, Y> {
switch (watchLogLevel) {
case WatchLogLevel.None:
@ -411,27 +415,27 @@ namespace ts {
}
}
function createFileWatcherWithLogging<H, T, U, V, X, Y>(host: H, file: string, cb: WatchCallback<U, V>, flags: T, passThrough: V | undefined, detailInfo1: X | undefined, detailInfo2: Y | undefined, addWatch: AddWatch<H, T, U, V>, log: (s: string) => void, watchCaption: string, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined): FileWatcher {
log(`${watchCaption}:: Added:: ${getWatchInfo(file, flags, detailInfo1, detailInfo2, getDetailWatchInfo)}`);
const watcher = createFileWatcherWithTriggerLogging(host, file, cb, flags, passThrough, detailInfo1, detailInfo2, addWatch, log, watchCaption, getDetailWatchInfo);
function createFileWatcherWithLogging<H, T, U, V, X, Y>(host: H, file: string, cb: WatchCallback<U, V>, flags: T, options: WatchOptions | undefined, passThrough: V | undefined, detailInfo1: X | undefined, detailInfo2: Y | undefined, addWatch: AddWatch<H, T, U, V>, log: (s: string) => void, watchCaption: string, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined): FileWatcher {
log(`${watchCaption}:: Added:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`);
const watcher = createFileWatcherWithTriggerLogging(host, file, cb, flags, options, passThrough, detailInfo1, detailInfo2, addWatch, log, watchCaption, getDetailWatchInfo);
return {
close: () => {
log(`${watchCaption}:: Close:: ${getWatchInfo(file, flags, detailInfo1, detailInfo2, getDetailWatchInfo)}`);
log(`${watchCaption}:: Close:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`);
watcher.close();
}
};
}
function createDirectoryWatcherWithLogging<H, T, U, V, X, Y>(host: H, file: string, cb: WatchCallback<U, V>, flags: T, passThrough: V | undefined, detailInfo1: X | undefined, detailInfo2: Y | undefined, addWatch: AddWatch<H, T, U, V>, log: (s: string) => void, watchCaption: string, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined): FileWatcher {
const watchInfo = `${watchCaption}:: Added:: ${getWatchInfo(file, flags, detailInfo1, detailInfo2, getDetailWatchInfo)}`;
function createDirectoryWatcherWithLogging<H, T, U, V, X, Y>(host: H, file: string, cb: WatchCallback<U, V>, flags: T, options: WatchOptions | undefined, passThrough: V | undefined, detailInfo1: X | undefined, detailInfo2: Y | undefined, addWatch: AddWatch<H, T, U, V>, log: (s: string) => void, watchCaption: string, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined): FileWatcher {
const watchInfo = `${watchCaption}:: Added:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`;
log(watchInfo);
const start = timestamp();
const watcher = createFileWatcherWithTriggerLogging(host, file, cb, flags, passThrough, detailInfo1, detailInfo2, addWatch, log, watchCaption, getDetailWatchInfo);
const watcher = createFileWatcherWithTriggerLogging(host, file, cb, flags, options, passThrough, detailInfo1, detailInfo2, addWatch, log, watchCaption, getDetailWatchInfo);
const elapsed = timestamp() - start;
log(`Elapsed:: ${elapsed}ms ${watchInfo}`);
return {
close: () => {
const watchInfo = `${watchCaption}:: Close:: ${getWatchInfo(file, flags, detailInfo1, detailInfo2, getDetailWatchInfo)}`;
const watchInfo = `${watchCaption}:: Close:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`;
log(watchInfo);
const start = timestamp();
watcher.close();
@ -441,19 +445,28 @@ namespace ts {
};
}
function createFileWatcherWithTriggerLogging<H, T, U, V, X, Y>(host: H, file: string, cb: WatchCallback<U, V>, flags: T, passThrough: V | undefined, detailInfo1: X | undefined, detailInfo2: Y | undefined, addWatch: AddWatch<H, T, U, V>, log: (s: string) => void, watchCaption: string, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined): FileWatcher {
function createFileWatcherWithTriggerLogging<H, T, U, V, X, Y>(host: H, file: string, cb: WatchCallback<U, V>, flags: T, options: WatchOptions | undefined, passThrough: V | undefined, detailInfo1: X | undefined, detailInfo2: Y | undefined, addWatch: AddWatch<H, T, U, V>, log: (s: string) => void, watchCaption: string, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined): FileWatcher {
return addWatch(host, file, (fileName, cbOptional) => {
const triggerredInfo = `${watchCaption}:: Triggered with ${fileName} ${cbOptional !== undefined ? cbOptional : ""}:: ${getWatchInfo(file, flags, detailInfo1, detailInfo2, getDetailWatchInfo)}`;
const triggerredInfo = `${watchCaption}:: Triggered with ${fileName} ${cbOptional !== undefined ? cbOptional : ""}:: ${getWatchInfo(file, flags, options, detailInfo1, detailInfo2, getDetailWatchInfo)}`;
log(triggerredInfo);
const start = timestamp();
cb(fileName, cbOptional, passThrough);
const elapsed = timestamp() - start;
log(`Elapsed:: ${elapsed}ms ${triggerredInfo}`);
}, flags);
}, flags, options);
}
function getWatchInfo<T, X, Y>(file: string, flags: T, detailInfo1: X, detailInfo2: Y | undefined, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined) {
return `WatchInfo: ${file} ${flags} ${getDetailWatchInfo ? getDetailWatchInfo(detailInfo1, detailInfo2) : detailInfo2 === undefined ? detailInfo1 : `${detailInfo1} ${detailInfo2}`}`;
export function getFallbackOptions(options: WatchOptions | undefined): WatchOptions {
const fallbackPolling = options?.fallbackPolling;
return {
watchFile: fallbackPolling !== undefined ?
fallbackPolling as unknown as WatchFileKind :
WatchFileKind.PriorityPollingInterval
};
}
function getWatchInfo<T, X, Y>(file: string, flags: T, options: WatchOptions | undefined, detailInfo1: X, detailInfo2: Y | undefined, getDetailWatchInfo: GetDetailWatchInfo<X, Y> | undefined) {
return `WatchInfo: ${file} ${flags} ${JSON.stringify(options)} ${getDetailWatchInfo ? getDetailWatchInfo(detailInfo1, detailInfo2) : detailInfo2 === undefined ? detailInfo1 : `${detailInfo1} ${detailInfo2}`}`;
}
export function closeFileWatcherOf<T extends { watcher: FileWatcher; }>(objWithWatcher: T) {

View file

@ -251,7 +251,7 @@ namespace ts {
fileName => getNormalizedAbsolutePath(fileName, currentDirectory)
);
if (configFileName) {
const configParseResult = parseConfigFileWithSystem(configFileName, commandLineOptions, sys, reportDiagnostic)!; // TODO: GH#18217
const configParseResult = parseConfigFileWithSystem(configFileName, commandLineOptions, commandLine.watchOptions, sys, reportDiagnostic)!; // TODO: GH#18217
if (commandLineOptions.showConfig) {
if (configParseResult.errors.length !== 0) {
reportDiagnostic = updateReportDiagnostic(
@ -277,7 +277,8 @@ namespace ts {
sys,
reportDiagnostic,
configParseResult,
commandLineOptions
commandLineOptions,
commandLine.watchOptions
);
}
else if (isIncrementalCompilation(configParseResult.options)) {
@ -314,7 +315,8 @@ namespace ts {
sys,
reportDiagnostic,
commandLine.fileNames,
commandLineOptions
commandLineOptions,
commandLine.watchOptions
);
}
else if (isIncrementalCompilation(commandLineOptions)) {
@ -389,6 +391,7 @@ namespace ts {
sys: System,
cb: ExecuteCommandLineCallbacks | undefined,
buildOptions: BuildOptions,
watchOptions: WatchOptions | undefined,
projects: string[],
errors: Diagnostic[]
) {
@ -437,7 +440,7 @@ namespace ts {
if (cb && cb.onSolutionBuilderHostCreate) cb.onSolutionBuilderHostCreate(buildHost);
updateCreateProgram(sys, buildHost);
buildHost.afterProgramEmitAndDiagnostics = program => reportStatistics(sys, program.getProgram());
const builder = createSolutionBuilderWithWatch(buildHost, projects, buildOptions);
const builder = createSolutionBuilderWithWatch(buildHost, projects, buildOptions, watchOptions);
builder.build();
return;
}
@ -463,12 +466,13 @@ namespace ts {
cb: ExecuteCommandLineCallbacks | undefined,
args: readonly string[]
) {
const { buildOptions, projects, errors } = parseBuildCommand(args);
const { buildOptions, watchOptions, projects, errors } = parseBuildCommand(args);
if (buildOptions.generateCpuProfile && sys.enableCPUProfiler) {
sys.enableCPUProfiler(buildOptions.generateCpuProfile, () => performBuildWorker(
sys,
cb,
buildOptions,
watchOptions,
projects,
errors
));
@ -478,6 +482,7 @@ namespace ts {
sys,
cb,
buildOptions,
watchOptions,
projects,
errors
);
@ -576,11 +581,13 @@ namespace ts {
sys: System,
reportDiagnostic: DiagnosticReporter,
configParseResult: ParsedCommandLine,
optionsToExtend: CompilerOptions
optionsToExtend: CompilerOptions,
watchOptionsToExtend: WatchOptions | undefined
) {
const watchCompilerHost = createWatchCompilerHostOfConfigFile(
configParseResult.options.configFilePath!,
optionsToExtend,
watchOptionsToExtend,
sys,
/*createProgram*/ undefined,
reportDiagnostic,
@ -595,11 +602,13 @@ namespace ts {
sys: System,
reportDiagnostic: DiagnosticReporter,
rootFiles: string[],
options: CompilerOptions
options: CompilerOptions,
watchOptions: WatchOptions | undefined
) {
const watchCompilerHost = createWatchCompilerHostOfFilesAndCompilerOptions(
rootFiles,
options,
watchOptions,
sys,
/*createProgram*/ undefined,
reportDiagnostic,

View file

@ -37,6 +37,8 @@ interface Array<T> { length: number; [n: number]: T; }`
newLine?: string;
windowsStyleRoot?: string;
environmentVariables?: Map<string>;
runWithoutRecursiveWatches?: boolean;
runWithFallbackPolling?: boolean;
}
export function createWatchedSystem(fileOrFolderList: readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters): TestServerHost {
@ -120,6 +122,11 @@ interface Array<T> { length: number; [n: number]: T; }`
}
}
function createWatcher<T>(map: MultiMap<T>, path: string, callback: T): FileWatcher {
map.add(path, callback);
return { close: () => map.remove(path, callback) };
}
function getDiffInKeys<T>(map: Map<T>, expectedKeys: readonly string[]) {
if (map.size === expectedKeys.length) {
return "";
@ -151,60 +158,99 @@ interface Array<T> { length: number; [n: number]: T; }`
assert.equal(map.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(map.keys())} Expected: ${expectedKeys}${getDiffInKeys(map, expectedKeys)}`);
}
function checkMapKeys(caption: string, map: Map<any>, expectedKeys: readonly string[]) {
verifyMapSize(caption, map, expectedKeys);
for (const name of expectedKeys) {
assert.isTrue(map.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(map.keys())}`);
}
}
export type MapValueTester<T, U> = [Map<U[]> | undefined, (value: T) => U];
export function checkMultiMapKeyCount(caption: string, actual: MultiMap<any>, expectedKeys: ReadonlyMap<number>): void;
export function checkMultiMapKeyCount(caption: string, actual: MultiMap<any>, expectedKeys: readonly string[], eachKeyCount: number): void;
export function checkMultiMapKeyCount(caption: string, actual: MultiMap<any>, expectedKeysMapOrArray: ReadonlyMap<number> | readonly string[], eachKeyCount?: number) {
const expectedKeys = isArray(expectedKeysMapOrArray) ? arrayToMap(expectedKeysMapOrArray, s => s, () => eachKeyCount!) : expectedKeysMapOrArray;
verifyMapSize(caption, actual, arrayFrom(expectedKeys.keys()));
export function checkMap<T, U = undefined>(caption: string, actual: MultiMap<T>, expectedKeys: ReadonlyMap<number>, valueTester?: MapValueTester<T,U>): void;
export function checkMap<T, U = undefined>(caption: string, actual: MultiMap<T>, expectedKeys: readonly string[], eachKeyCount: number, valueTester?: MapValueTester<T, U>): void;
export function checkMap<T>(caption: string, actual: Map<T> | MultiMap<T>, expectedKeys: readonly string[], eachKeyCount: undefined): void;
export function checkMap<T, U = undefined>(
caption: string,
actual: Map<T> | MultiMap<T>,
expectedKeysMapOrArray: ReadonlyMap<number> | readonly string[],
eachKeyCountOrValueTester?: number | MapValueTester<T, U>,
valueTester?: MapValueTester<T, U>) {
const expectedKeys = isArray(expectedKeysMapOrArray) ? arrayToMap(expectedKeysMapOrArray, s => s, () => eachKeyCountOrValueTester as number) : expectedKeysMapOrArray;
verifyMapSize(caption, actual, isArray(expectedKeysMapOrArray) ? expectedKeysMapOrArray : arrayFrom(expectedKeys.keys()));
if (!isNumber(eachKeyCountOrValueTester)) {
valueTester = eachKeyCountOrValueTester;
}
const [expectedValues, valueMapper] = valueTester || [undefined, undefined!];
expectedKeys.forEach((count, name) => {
assert.isTrue(actual.has(name), `${caption}: expected to contain ${name}, actual keys: ${arrayFrom(actual.keys())}`);
assert.equal(actual.get(name)!.length, count, `${caption}: Expected to be have ${count} entries for ${name}. Actual entry: ${JSON.stringify(actual.get(name))}`);
// Check key information only if eachKeyCount is provided
if (!isArray(expectedKeysMapOrArray) || eachKeyCountOrValueTester !== undefined) {
assert.equal((actual as MultiMap<T>).get(name)!.length, count, `${caption}: Expected to be have ${count} entries for ${name}. Actual entry: ${JSON.stringify(actual.get(name))}`);
if (expectedValues) {
assert.deepEqual(
(actual as MultiMap<T>).get(name)!.map(valueMapper),
expectedValues.get(name),
`${caption}:: expected values mismatch for ${name}`
);
}
}
});
}
export function checkArray(caption: string, actual: readonly string[], expected: readonly string[]) {
checkMapKeys(caption, arrayToMap(actual, identity), expected);
assert.equal(actual.length, expected.length, `${caption}: incorrect actual number of files, expected:\r\n${expected.join("\r\n")}\r\ngot: ${actual.join("\r\n")}`);
for (const f of expected) {
assert.isTrue(contains(actual, f), `${caption}: expected to find ${f} in ${actual}`);
}
checkMap(caption, arrayToMap(actual, identity), expected, /*eachKeyCount*/ undefined);
}
export function checkWatchedFiles(host: TestServerHost, expectedFiles: string[], additionalInfo?: string) {
checkMapKeys(`watchedFiles:: ${additionalInfo || ""}::`, host.watchedFiles, expectedFiles);
checkMap(`watchedFiles:: ${additionalInfo || ""}::`, host.watchedFiles, expectedFiles, /*eachKeyCount*/ undefined);
}
export function checkWatchedFilesDetailed(host: TestServerHost, expectedFiles: ReadonlyMap<number>): void;
export function checkWatchedFilesDetailed(host: TestServerHost, expectedFiles: readonly string[], eachFileWatchCount: number): void;
export function checkWatchedFilesDetailed(host: TestServerHost, expectedFiles: ReadonlyMap<number> | readonly string[], eachFileWatchCount?: number) {
export function checkWatchedFilesDetailed(host: TestServerHost, expectedFiles: ReadonlyMap<number>, expectedPollingIntervals?: Map<PollingInterval[]>): void;
export function checkWatchedFilesDetailed(host: TestServerHost, expectedFiles: readonly string[], eachFileWatchCount: number, expectedPollingIntervals?: Map<PollingInterval[]>): void;
export function checkWatchedFilesDetailed(host: TestServerHost, expectedFiles: ReadonlyMap<number> | readonly string[], eachFileWatchCount?: number | Map<PollingInterval[]>, expectedPollingIntervals?: Map<PollingInterval[]>) {
if (!isNumber(eachFileWatchCount)) expectedPollingIntervals = eachFileWatchCount;
if (isArray(expectedFiles)) {
checkMultiMapKeyCount("watchedFiles", host.watchedFiles, expectedFiles, eachFileWatchCount!);
checkMap(
"watchedFiles",
host.watchedFiles,
expectedFiles,
eachFileWatchCount as number,
[expectedPollingIntervals, ({ pollingInterval }) => pollingInterval]
);
}
else {
checkMultiMapKeyCount("watchedFiles", host.watchedFiles, expectedFiles);
checkMap(
"watchedFiles",
host.watchedFiles,
expectedFiles,
[expectedPollingIntervals, ({ pollingInterval }) => pollingInterval]
);
}
}
export function checkWatchedDirectories(host: TestServerHost, expectedDirectories: string[], recursive: boolean) {
checkMapKeys(`watchedDirectories${recursive ? " recursive" : ""}`, recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories);
checkMap(`watchedDirectories${recursive ? " recursive" : ""}`, recursive ? host.fsWatchesRecursive : host.fsWatches, expectedDirectories, /*eachKeyCount*/ undefined);
}
export function checkWatchedDirectoriesDetailed(host: TestServerHost, expectedDirectories: ReadonlyMap<number>, recursive: boolean): void;
export function checkWatchedDirectoriesDetailed(host: TestServerHost, expectedDirectories: readonly string[], eachDirectoryWatchCount: number, recursive: boolean): void;
export function checkWatchedDirectoriesDetailed(host: TestServerHost, expectedDirectories: ReadonlyMap<number> | readonly string[], recursiveOrEachDirectoryWatchCount: boolean | number, recursive?: boolean) {
export interface FallbackPollingOptions {
fallbackPollingInterval: PollingInterval;
fallbackOptions: WatchOptions | undefined;
}
export function checkWatchedDirectoriesDetailed(host: TestServerHost, expectedDirectories: ReadonlyMap<number>, recursive: boolean, expectedFallbacks?: Map<FallbackPollingOptions[]>): void;
export function checkWatchedDirectoriesDetailed(host: TestServerHost, expectedDirectories: readonly string[], eachDirectoryWatchCount: number, recursive: boolean, expectedFallbacks?: Map<FallbackPollingOptions[]>): void;
export function checkWatchedDirectoriesDetailed(host: TestServerHost, expectedDirectories: ReadonlyMap<number> | readonly string[], recursiveOrEachDirectoryWatchCount: boolean | number, recursiveOrExpectedFallbacks?: boolean | Map<FallbackPollingOptions[]>, expectedFallbacks?: Map<FallbackPollingOptions[]>) {
if (typeof recursiveOrExpectedFallbacks !== "boolean") expectedFallbacks = recursiveOrExpectedFallbacks;
if (isArray(expectedDirectories)) {
checkMultiMapKeyCount(`watchedDirectories${recursive ? " recursive" : ""}`, recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories, recursiveOrEachDirectoryWatchCount as number);
checkMap(
`fsWatches${recursiveOrExpectedFallbacks ? " recursive" : ""}`,
recursiveOrExpectedFallbacks as boolean ? host.fsWatchesRecursive : host.fsWatches,
expectedDirectories,
recursiveOrEachDirectoryWatchCount as number,
[expectedFallbacks, ({ fallbackPollingInterval, fallbackOptions }) => ({ fallbackPollingInterval, fallbackOptions })]
);
}
else {
recursive = recursiveOrEachDirectoryWatchCount as boolean;
checkMultiMapKeyCount(`watchedDirectories${recursive ? " recursive" : ""}`, recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories);
recursiveOrExpectedFallbacks = recursiveOrEachDirectoryWatchCount as boolean;
checkMap(
`fsWatches{recursive ? " recursive" : ""}`,
recursiveOrExpectedFallbacks ? host.fsWatchesRecursive : host.fsWatches,
expectedDirectories,
[expectedFallbacks, ({ fallbackPollingInterval, fallbackOptions }) => ({ fallbackPollingInterval, fallbackOptions })]
);
}
}
@ -279,11 +325,13 @@ interface Array<T> { length: number; [n: number]: T; }`
export interface TestFileWatcher {
cb: FileWatcherCallback;
fileName: string;
pollingInterval: PollingInterval;
}
export interface TestDirectoryWatcher {
cb: DirectoryWatcherCallback;
directoryName: string;
export interface TestFsWatcher {
cb: FsWatchCallback;
fallbackPollingInterval: PollingInterval;
fallbackOptions: WatchOptions | undefined;
}
export interface ReloadWatchInvokeOptions {
@ -330,25 +378,26 @@ interface Array<T> { length: number; [n: number]: T; }`
private immediateCallbacks = new Callbacks();
readonly screenClears: number[] = [];
readonly watchedDirectories = createMultiMap<TestDirectoryWatcher>();
readonly watchedDirectoriesRecursive = createMultiMap<TestDirectoryWatcher>();
readonly watchedFiles = createMultiMap<TestFileWatcher>();
readonly fsWatches = createMultiMap<TestFsWatcher>();
readonly fsWatchesRecursive = createMultiMap<TestFsWatcher>();
runWithFallbackPolling: boolean;
public readonly useCaseSensitiveFileNames: boolean;
public readonly newLine: string;
public readonly windowsStyleRoot?: string;
private readonly environmentVariables?: Map<string>;
private readonly executingFilePath: string;
private readonly currentDirectory: string;
private readonly customWatchFile: HostWatchFile | undefined;
private readonly customRecursiveWatchDirectory: HostWatchDirectory | undefined;
public require: ((initialPath: string, moduleName: string) => RequireResult) | undefined;
watchFile: HostWatchFile;
watchDirectory: HostWatchDirectory;
constructor(
public withSafeList: boolean,
fileOrFolderorSymLinkList: readonly FileOrFolderOrSymLink[],
{
useCaseSensitiveFileNames, executingFilePath, currentDirectory,
newLine, windowsStyleRoot, environmentVariables
newLine, windowsStyleRoot, environmentVariables,
runWithoutRecursiveWatches, runWithFallbackPolling
}: TestServerHostCreationParameters = {}) {
this.useCaseSensitiveFileNames = !!useCaseSensitiveFileNames;
this.newLine = newLine || "\n";
@ -359,56 +408,35 @@ interface Array<T> { length: number; [n: number]: T; }`
this.toPath = s => toPath(s, currentDirectory, this.getCanonicalFileName);
this.executingFilePath = this.getHostSpecificPath(executingFilePath || getExecutingFilePathFromLibFile());
this.currentDirectory = this.getHostSpecificPath(currentDirectory);
this.reloadFS(fileOrFolderorSymLinkList);
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.runWithFallbackPolling = !!runWithFallbackPolling;
const tscWatchFile = this.environmentVariables && this.environmentVariables.get("TSC_WATCHFILE");
const tscWatchDirectory = this.environmentVariables && this.environmentVariables.get("TSC_WATCHDIRECTORY");
const { watchFile, watchDirectory } = createSystemWatchFunctions({
// 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
);
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);
this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({
useCaseSensitiveFileNames: this.useCaseSensitiveFileNames,
directoryExists: path => this.directoryExists(path),
getAccessibleSortedChildDirectories: path => this.getDirectories(path),
watchDirectory,
realpath: s => this.realpath(s)
});
}
else if (tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory) {
const watchDirectory: HostWatchDirectory = (directory, cb) => this.watchDirectory(directory, fileName => cb(fileName), /*recursive*/ false);
this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({
useCaseSensitiveFileNames: this.useCaseSensitiveFileNames,
directoryExists: path => this.directoryExists(path),
getAccessibleSortedChildDirectories: path => this.getDirectories(path),
watchDirectory,
realpath: s => this.realpath(s)
});
}
else if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) {
const watchFile = createDynamicPriorityPollingWatchFile(this);
const watchDirectory: HostWatchDirectory = (directory, cb) => watchFile(directory, () => cb(directory), PollingInterval.Medium);
this.customRecursiveWatchDirectory = createRecursiveDirectoryWatcher({
useCaseSensitiveFileNames: this.useCaseSensitiveFileNames,
directoryExists: path => this.directoryExists(path),
getAccessibleSortedChildDirectories: path => this.getDirectories(path),
watchDirectory,
realpath: s => this.realpath(s)
});
}
) :
this.watchFileWorker.bind(this),
getModifiedTime: this.getModifiedTime.bind(this),
setTimeout: this.setTimeout.bind(this),
clearTimeout: this.clearTimeout.bind(this),
fsWatch: this.fsWatch.bind(this),
fileExists: this.fileExists.bind(this),
useCaseSensitiveFileNames: this.useCaseSensitiveFileNames,
fsSupportsRecursiveFsWatch: tscWatchDirectory ? false : !runWithoutRecursiveWatches,
directoryExists: this.directoryExists.bind(this),
getAccessibleSortedChildDirectories: path => this.getDirectories(path),
realpath: this.realpath.bind(this),
tscWatchFile,
tscWatchDirectory
});
this.watchFile = watchFile;
this.watchDirectory = watchDirectory;
this.reloadFS(fileOrFolderorSymLinkList);
}
getNewLine() {
@ -473,6 +501,7 @@ interface Array<T> { length: number; [n: number]: T; }`
else {
// Folder update: Nothing to do.
currentEntry.modifiedTime = this.now();
this.invokeFsWatches(currentEntry.fullPath, "change");
}
}
}
@ -510,10 +539,13 @@ interface Array<T> { length: number; [n: number]: T; }`
currentEntry.modifiedTime = this.now();
this.fs.get(getDirectoryPath(currentEntry.path))!.modifiedTime = this.now();
if (options && options.invokeDirectoryWatcherInsteadOfFileChanged) {
this.invokeDirectoryWatcher(getDirectoryPath(currentEntry.fullPath), currentEntry.fullPath);
const directoryFullPath = getDirectoryPath(currentEntry.fullPath);
this.invokeFileWatcher(directoryFullPath, FileWatcherEventKind.Changed, /*useFileNameInCallback*/ true);
this.invokeFsWatchesCallbacks(directoryFullPath, "rename", currentEntry.fullPath);
this.invokeRecursiveFsWatches(directoryFullPath, "rename", currentEntry.fullPath);
}
else {
this.invokeFileWatcher(currentEntry.fullPath, FileWatcherEventKind.Changed);
this.invokeFileAndFsWatches(currentEntry.fullPath, FileWatcherEventKind.Changed);
}
}
}
@ -564,7 +596,7 @@ interface Array<T> { length: number; [n: number]: T; }`
private renameFolderEntries(oldFolder: FsFolder, newFolder: FsFolder) {
for (const entry of oldFolder.entries) {
this.fs.delete(entry.path);
this.invokeFileWatcher(entry.fullPath, FileWatcherEventKind.Deleted);
this.invokeFileAndFsWatches(entry.fullPath, FileWatcherEventKind.Deleted);
entry.fullPath = combinePaths(newFolder.fullPath, getBaseFileName(entry.fullPath));
entry.path = this.toPath(entry.fullPath);
@ -572,7 +604,7 @@ interface Array<T> { length: number; [n: number]: T; }`
newFolder.entries.push(entry);
}
this.fs.set(entry.path, entry);
this.invokeFileWatcher(entry.fullPath, FileWatcherEventKind.Created);
this.invokeFileAndFsWatches(entry.fullPath, FileWatcherEventKind.Created);
if (isFsFolder(entry)) {
this.renameFolderEntries(entry, entry);
}
@ -631,12 +663,8 @@ interface Array<T> { length: number; [n: number]: T; }`
if (ignoreWatch) {
return;
}
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Created);
if (isFsFolder(fileOrDirectory)) {
this.invokeDirectoryWatcher(fileOrDirectory.fullPath, fileOrDirectory.fullPath);
this.invokeWatchedDirectoriesRecursiveCallback(fileOrDirectory.fullPath, fileOrDirectory.fullPath);
}
this.invokeDirectoryWatcher(folder.fullPath, fileOrDirectory.fullPath);
this.invokeFileAndFsWatches(fileOrDirectory.fullPath, FileWatcherEventKind.Created);
this.invokeFileAndFsWatches(folder.fullPath, FileWatcherEventKind.Changed);
}
private removeFileOrFolder(fileOrDirectory: FsFile | FsFolder | FsSymLink, isRemovableLeafFolder: (folder: FsFolder) => boolean, isRenaming = false) {
@ -649,23 +677,15 @@ interface Array<T> { length: number; [n: number]: T; }`
}
this.fs.delete(fileOrDirectory.path);
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted);
if (isFsFolder(fileOrDirectory)) {
Debug.assert(fileOrDirectory.entries.length === 0 || isRenaming);
// Invoke directory and recursive directory watcher for the folder
// Here we arent invoking recursive directory watchers for the base folders
// since that is something we would want to do for both file as well as folder we are deleting
this.invokeWatchedDirectoriesCallback(fileOrDirectory.fullPath, "");
this.invokeWatchedDirectoriesRecursiveCallback(fileOrDirectory.fullPath, "");
}
if (basePath !== fileOrDirectory.path) {
if (baseFolder.entries.length === 0 && isRemovableLeafFolder(baseFolder)) {
this.removeFileOrFolder(baseFolder, isRemovableLeafFolder);
}
else {
this.invokeRecursiveDirectoryWatcher(baseFolder.fullPath, fileOrDirectory.fullPath);
}
this.invokeFileAndFsWatches(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted);
this.invokeFileAndFsWatches(baseFolder.fullPath, FileWatcherEventKind.Changed);
if (basePath !== fileOrDirectory.path &&
baseFolder.entries.length === 0 &&
isRemovableLeafFolder(baseFolder)) {
this.removeFileOrFolder(baseFolder, isRemovableLeafFolder);
}
}
@ -694,50 +714,74 @@ interface Array<T> { length: number; [n: number]: T; }`
this.removeFileOrFolder(currentEntry, returnFalse);
}
// For overriding the methods
invokeWatchedDirectoriesCallback(folderFullPath: string, relativePath: string) {
invokeWatcherCallbacks(this.watchedDirectories.get(this.toPath(folderFullPath)), cb => this.directoryCallback(cb, relativePath));
private watchFileWorker(fileName: string, cb: FileWatcherCallback, pollingInterval: PollingInterval) {
return createWatcher(
this.watchedFiles,
this.toFullPath(fileName),
{ fileName, cb, pollingInterval }
);
}
invokeWatchedDirectoriesRecursiveCallback(folderFullPath: string, relativePath: string) {
invokeWatcherCallbacks(this.watchedDirectoriesRecursive.get(this.toPath(folderFullPath)), cb => this.directoryCallback(cb, relativePath));
private fsWatch(
fileOrDirectory: string,
_entryKind: FileSystemEntryKind,
cb: FsWatchCallback,
recursive: boolean,
fallbackPollingInterval: PollingInterval,
fallbackOptions: WatchOptions | undefined): FileWatcher {
return this.runWithFallbackPolling ?
this.watchFile(
fileOrDirectory,
createFileWatcherCallback(cb),
fallbackPollingInterval,
fallbackOptions
) :
createWatcher(
recursive ? this.fsWatchesRecursive : this.fsWatches,
this.toFullPath(fileOrDirectory),
{ cb, fallbackPollingInterval, fallbackOptions }
);
}
private invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind, useFileNameInCallback?: boolean) {
invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind, useFileNameInCallback?: boolean) {
invokeWatcherCallbacks(this.watchedFiles.get(this.toPath(fileFullPath)), ({ cb, fileName }) => cb(useFileNameInCallback ? fileName : fileFullPath, eventKind));
}
private fsWatchCallback(map: MultiMap<TestFsWatcher>, fullPath: string, eventName: "rename" | "change", entryFullPath?: string) {
invokeWatcherCallbacks(map.get(this.toPath(fullPath)), ({ cb }) => cb(eventName, entryFullPath ? this.getRelativePathToDirectory(fullPath, entryFullPath) : ""));
}
invokeFsWatchesCallbacks(fullPath: string, eventName: "rename" | "change", entryFullPath?: string) {
this.fsWatchCallback(this.fsWatches, fullPath, eventName, entryFullPath);
}
invokeFsWatchesRecursiveCallbacks(fullPath: string, eventName: "rename" | "change", entryFullPath?: string) {
this.fsWatchCallback(this.fsWatchesRecursive, fullPath, eventName, entryFullPath);
}
private getRelativePathToDirectory(directoryFullPath: string, fileFullPath: string) {
return getRelativePathToDirectoryOrUrl(directoryFullPath, fileFullPath, this.currentDirectory, this.getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
}
/**
* This will call the directory watcher for the folderFullPath and recursive directory watchers for this and base folders
*/
private invokeDirectoryWatcher(folderFullPath: string, fileName: string) {
const relativePath = this.getRelativePathToDirectory(folderFullPath, fileName);
// Folder is changed when the directory watcher is invoked
this.invokeFileWatcher(folderFullPath, FileWatcherEventKind.Changed, /*useFileNameInCallback*/ true);
this.invokeWatchedDirectoriesCallback(folderFullPath, relativePath);
this.invokeRecursiveDirectoryWatcher(folderFullPath, fileName);
}
private directoryCallback({ cb, directoryName }: TestDirectoryWatcher, relativePath: string) {
cb(combinePaths(directoryName, relativePath));
}
/**
* This will call the recursive directory watcher for this directory as well as all the base directories
*/
private invokeRecursiveDirectoryWatcher(fullPath: string, fileName: string) {
const relativePath = this.getRelativePathToDirectory(fullPath, fileName);
this.invokeWatchedDirectoriesRecursiveCallback(fullPath, relativePath);
private invokeRecursiveFsWatches(fullPath: string, eventName: "rename" | "change", entryFullPath?: string) {
this.invokeFsWatchesRecursiveCallbacks(fullPath, eventName, entryFullPath);
const basePath = getDirectoryPath(fullPath);
if (this.getCanonicalFileName(fullPath) !== this.getCanonicalFileName(basePath)) {
this.invokeRecursiveDirectoryWatcher(basePath, fileName);
this.invokeRecursiveFsWatches(basePath, eventName, entryFullPath || fullPath);
}
}
private invokeFsWatches(fullPath: string, eventName: "rename" | "change") {
this.invokeFsWatchesCallbacks(fullPath, eventName);
this.invokeFsWatchesCallbacks(getDirectoryPath(fullPath), eventName, fullPath);
this.invokeRecursiveFsWatches(fullPath, eventName);
}
private invokeFileAndFsWatches(fileOrFolderFullPath: string, eventKind: FileWatcherEventKind) {
this.invokeFileWatcher(fileOrFolderFullPath, eventKind);
this.invokeFsWatches(fileOrFolderFullPath, eventKind === FileWatcherEventKind.Changed ? "change" : "rename");
}
private toFsEntry(path: string): FSEntryBase {
const fullPath = getNormalizedAbsolutePath(path, this.currentDirectory);
return {
@ -874,22 +918,6 @@ interface Array<T> { length: number; [n: number]: T; }`
}, path => this.realpath(path));
}
watchDirectory(directoryName: string, cb: DirectoryWatcherCallback, recursive: boolean): FileWatcher {
if (recursive && this.customRecursiveWatchDirectory) {
return this.customRecursiveWatchDirectory(directoryName, cb, /*recursive*/ true);
}
const path = this.toFullPath(directoryName);
const map = recursive ? this.watchedDirectoriesRecursive : this.watchedDirectories;
const callback: TestDirectoryWatcher = {
cb,
directoryName
};
map.add(path, callback);
return {
close: () => map.remove(path, callback)
};
}
createHash(s: string): string {
return Harness.mockHash(s);
}
@ -898,21 +926,6 @@ interface Array<T> { length: number; [n: number]: T; }`
return sys.createSHA256Hash!(s);
}
watchFile(fileName: string, cb: FileWatcherCallback, pollingInterval: number) {
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);
return { close: () => this.watchedFiles.remove(path, callback) };
}
// TOOD: record and invoke callbacks to simulate timer events
setTimeout(callback: TimeOutCallback, _time: number, ...args: any[]) {
return this.timeoutCallbacks.register(callback, args);

View file

@ -14,6 +14,7 @@ declare namespace ts.server {
readonly fileNames: string[];
readonly projectRootPath: Path;
readonly compilerOptions: CompilerOptions;
readonly watchOptions?: WatchOptions;
readonly typeAcquisition: TypeAcquisition;
readonly unresolvedImports: SortedReadonlyArray<string>;
readonly cachePath?: string;
@ -81,8 +82,8 @@ declare namespace ts.server {
useCaseSensitiveFileNames: boolean;
writeFile(path: string, content: string): void;
createDirectory(path: string): void;
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: CompilerOptions): FileWatcher;
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: CompilerOptions): FileWatcher;
}
export interface SetTypings extends ProjectResponse {

View file

@ -170,6 +170,7 @@ namespace ts.server {
}
const compilerOptionConverters = prepareConvertersForEnumLikeCompilerOptions(optionDeclarations);
const watchOptionsConverters = prepareConvertersForEnumLikeCompilerOptions(optionsForWatch);
const indentStyle = createMapFromTemplate({
none: IndentStyle.None,
block: IndentStyle.Block,
@ -247,6 +248,18 @@ namespace ts.server {
return <any>protocolOptions;
}
export function convertWatchOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): WatchOptions | undefined {
let result: WatchOptions | undefined;
watchOptionsConverters.forEach((mappedValues, id) => {
const propertyValue = protocolOptions[id];
if (propertyValue === undefined) return;
(result || (result = {}))[id] = isString(propertyValue) ?
mappedValues.get(propertyValue.toLowerCase()) :
propertyValue;
});
return result;
}
export function tryConvertScriptKindName(scriptKindName: protocol.ScriptKindName | ScriptKind): ScriptKind {
return isString(scriptKindName) ? convertScriptKindName(scriptKindName) : scriptKindName;
}
@ -277,6 +290,7 @@ namespace ts.server {
preferences: protocol.UserPreferences;
hostInfo: string;
extraFileExtensions?: FileExtensionInfo[];
watchOptions?: WatchOptions;
}
export interface OpenConfiguredProjectResult {
@ -545,6 +559,8 @@ namespace ts.server {
private compilerOptionsForInferredProjects: CompilerOptions | undefined;
private compilerOptionsForInferredProjectsPerProjectRoot = createMap<CompilerOptions>();
private watchOptionsForInferredProjects: WatchOptions | undefined;
private watchOptionsForInferredProjectsPerProjectRoot = createMap<WatchOptions | false>();
/**
* Project size for configured or external projects
*/
@ -638,7 +654,7 @@ namespace ts.server {
formatCodeOptions: getDefaultFormatCodeSettings(this.host.newLine),
preferences: emptyOptions,
hostInfo: "Unknown host",
extraFileExtensions: []
extraFileExtensions: [],
};
this.documentRegistry = createDocumentRegistryInternal(this.host.useCaseSensitiveFileNames, this.currentDirectory, this);
@ -852,6 +868,7 @@ namespace ts.server {
Debug.assert(projectRootPath === undefined || this.useInferredProjectPerProjectRoot, "Setting compiler options per project root path is only supported when useInferredProjectPerProjectRoot is enabled");
const compilerOptions = convertCompilerOptions(projectCompilerOptions);
const watchOptions = convertWatchOptions(projectCompilerOptions);
// always set 'allowNonTsExtensions' for inferred projects since user cannot configure it from the outside
// previously we did not expose a way for user to change these settings and this option was enabled by default
@ -859,9 +876,11 @@ namespace ts.server {
const canonicalProjectRootPath = projectRootPath && this.toCanonicalFileName(projectRootPath);
if (canonicalProjectRootPath) {
this.compilerOptionsForInferredProjectsPerProjectRoot.set(canonicalProjectRootPath, compilerOptions);
this.watchOptionsForInferredProjectsPerProjectRoot.set(canonicalProjectRootPath, watchOptions || false);
}
else {
this.compilerOptionsForInferredProjects = compilerOptions;
this.watchOptionsForInferredProjects = watchOptions;
}
for (const project of this.inferredProjects) {
@ -877,6 +896,7 @@ namespace ts.server {
project.projectRootPath === canonicalProjectRootPath :
!project.projectRootPath || !this.compilerOptionsForInferredProjectsPerProjectRoot.has(project.projectRootPath)) {
project.setCompilerOptions(compilerOptions);
project.setWatchOptions(watchOptions);
project.compileOnSaveEnabled = compilerOptions.compileOnSave!;
project.markAsDirty();
this.delayUpdateProjectGraph(project);
@ -1101,6 +1121,7 @@ namespace ts.server {
}
},
flags,
this.getWatchOptions(project),
WatchType.WildcardDirectory,
project
);
@ -1112,7 +1133,8 @@ namespace ts.server {
return this.configFileExistenceInfoCache.get(project.canonicalConfigFilePath)!;
}
private onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) {
/*@internal*/
onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) {
const configFileExistenceInfo = this.getConfigFileExistenceInfo(project);
if (eventKind === FileWatcherEventKind.Deleted) {
// Update the cached status
@ -1464,6 +1486,7 @@ namespace ts.server {
configFileName,
(_filename, eventKind) => this.onConfigFileChangeForOpenScriptInfo(configFileName, eventKind),
PollingInterval.High,
this.hostConfiguration.watchOptions,
WatchType.ConfigFileForInferredRoot
) :
noopFileWatcher;
@ -1727,6 +1750,7 @@ namespace ts.server {
private createExternalProject(projectFileName: string, files: protocol.ExternalFile[], options: protocol.ExternalProjectCompilerOptions, typeAcquisition: TypeAcquisition, excludedFiles: NormalizedPath[]) {
const compilerOptions = convertCompilerOptions(options);
const watchOptions = convertWatchOptions(options);
const project = new ExternalProject(
projectFileName,
this,
@ -1734,7 +1758,10 @@ namespace ts.server {
compilerOptions,
/*lastFileExceededProgramSize*/ this.getFilenameForExceededTotalSizeLimitForNonTsFiles(projectFileName, compilerOptions, files, externalFilePropertyReader),
options.compileOnSave === undefined ? true : options.compileOnSave,
/*projectFilePath*/ undefined, this.currentPluginConfigOverrides);
/*projectFilePath*/ undefined,
this.currentPluginConfigOverrides,
watchOptions
);
project.excludedFiles = excludedFiles;
this.addFilesToNonInferredProject(project, files, externalFilePropertyReader, typeAcquisition);
@ -1805,14 +1832,7 @@ namespace ts.server {
this.documentRegistry,
cachedDirectoryStructureHost);
// TODO: We probably should also watch the configFiles that are extended
project.configFileWatcher = this.watchFactory.watchFile(
this.host,
configFileName,
(_fileName, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind),
PollingInterval.High,
WatchType.ConfigFile,
project
);
project.createConfigFileWatcher();
this.configuredProjects.set(project.canonicalConfigFilePath, project);
this.setConfigFileExistenceByNewConfiguredProject(project);
return project;
@ -1864,7 +1884,9 @@ namespace ts.server {
/*existingOptions*/ {},
configFilename,
/*resolutionStack*/[],
this.hostConfiguration.extraFileExtensions);
this.hostConfiguration.extraFileExtensions,
/*extendedConfigCache*/ undefined,
);
if (parsedCommandLine.errors.length) {
configFileErrors.push(...parsedCommandLine.errors);
@ -1898,12 +1920,14 @@ namespace ts.server {
project.stopWatchingWildCards();
}
else {
project.setCompilerOptions(compilerOptions);
project.setWatchOptions(parsedCommandLine.watchOptions);
project.enableLanguageService();
project.watchWildcards(createMapFromTemplate(parsedCommandLine.wildcardDirectories!)); // TODO: GH#18217
}
project.enablePluginsWithOptions(compilerOptions, this.currentPluginConfigOverrides);
const filesToAdd = parsedCommandLine.fileNames.concat(project.getExternalFiles());
this.updateRootAndOptionsOfNonInferredProject(project, filesToAdd, fileNamePropertyReader, compilerOptions, parsedCommandLine.typeAcquisition!, parsedCommandLine.compileOnSave);
this.updateRootAndOptionsOfNonInferredProject(project, filesToAdd, fileNamePropertyReader, compilerOptions, parsedCommandLine.typeAcquisition!, parsedCommandLine.compileOnSave, parsedCommandLine.watchOptions);
}
private updateNonInferredProjectFiles<T>(project: ExternalProject | ConfiguredProject, files: T[], propertyReader: FilePropertyReader<T>) {
@ -1979,8 +2003,9 @@ namespace ts.server {
project.markAsDirty();
}
private updateRootAndOptionsOfNonInferredProject<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean | undefined) {
private updateRootAndOptionsOfNonInferredProject<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean | undefined, watchOptions: WatchOptions | undefined) {
project.setCompilerOptions(newOptions);
project.setWatchOptions(watchOptions);
// VS only set the CompileOnSaveEnabled option in the request if the option was changed recently
// therefore if it is undefined, it should not be updated.
if (compileOnSave !== undefined) {
@ -2108,7 +2133,14 @@ namespace ts.server {
private createInferredProject(currentDirectory: string | undefined, isSingleInferredProject?: boolean, projectRootPath?: NormalizedPath): InferredProject {
const compilerOptions = projectRootPath && this.compilerOptionsForInferredProjectsPerProjectRoot.get(projectRootPath) || this.compilerOptionsForInferredProjects!; // TODO: GH#18217
const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath, currentDirectory, this.currentPluginConfigOverrides);
let watchOptions: WatchOptions | false | undefined;
if (projectRootPath) {
watchOptions = this.watchOptionsForInferredProjectsPerProjectRoot.get(projectRootPath);
}
if (watchOptions === undefined) {
watchOptions = this.watchOptionsForInferredProjects;
}
const project = new InferredProject(this, this.documentRegistry, compilerOptions, watchOptions || undefined, projectRootPath, currentDirectory, this.currentPluginConfigOverrides);
if (isSingleInferredProject) {
this.inferredProjects.unshift(project);
}
@ -2197,6 +2229,7 @@ namespace ts.server {
info.fileName,
(fileName, eventKind, path) => this.onSourceFileChanged(fileName, eventKind, path),
PollingInterval.Medium,
this.hostConfiguration.watchOptions,
info.path,
WatchType.ClosedScriptInfo
);
@ -2243,6 +2276,7 @@ namespace ts.server {
}
},
WatchDirectoryFlags.Recursive,
this.hostConfiguration.watchOptions,
WatchType.NodeModulesForClosedScriptInfo
);
const result: ScriptInfoInNodeModulesWatcher = {
@ -2470,6 +2504,7 @@ namespace ts.server {
}
},
PollingInterval.High,
this.hostConfiguration.watchOptions,
WatchType.MissingSourceMapFile,
);
return fileWatcher;
@ -2554,9 +2589,22 @@ namespace ts.server {
this.reloadProjects();
this.logger.info("Host file extension mappings updated");
}
if (args.watchOptions) {
this.hostConfiguration.watchOptions = convertWatchOptions(args.watchOptions);
this.logger.info(`Host watch options changed to ${JSON.stringify(this.hostConfiguration.watchOptions)}, it will be take effect for next watches.`);
}
}
}
/*@internal*/
getWatchOptions(project: Project) {
const projectOptions = project.getWatchOptions();
return projectOptions && this.hostConfiguration.watchOptions ?
{ ...this.hostConfiguration.watchOptions, ...projectOptions } :
projectOptions || this.hostConfiguration.watchOptions;
}
closeLog() {
this.logger.close();
}
@ -3344,6 +3392,7 @@ namespace ts.server {
externalProject.excludedFiles = excludedFiles;
if (!tsConfigFiles) {
const compilerOptions = convertCompilerOptions(proj.options);
const watchOptions = convertWatchOptions(proj.options);
const lastFileExceededProgramSize = this.getFilenameForExceededTotalSizeLimitForNonTsFiles(proj.projectFileName, compilerOptions, proj.rootFiles, externalFilePropertyReader);
if (lastFileExceededProgramSize) {
externalProject.disableLanguageService(lastFileExceededProgramSize);
@ -3353,7 +3402,7 @@ namespace ts.server {
}
// external project already exists and not config files were added - update the project and return;
// The graph update here isnt postponed since any file open operation needs all updated external projects
this.updateRootAndOptionsOfNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, compilerOptions, proj.typeAcquisition, proj.options.compileOnSave);
this.updateRootAndOptionsOfNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, compilerOptions, proj.typeAcquisition, proj.options.compileOnSave, watchOptions);
externalProject.updateGraph();
return;
}

View file

@ -257,6 +257,7 @@ namespace ts.server {
lastFileExceededProgramSize: string | undefined,
private compilerOptions: CompilerOptions,
public compileOnSaveEnabled: boolean,
protected watchOptions: WatchOptions | undefined,
directoryStructureHost: DirectoryStructureHost,
currentDirectory: string | undefined,
customRealpath?: (s: string) => string) {
@ -472,6 +473,7 @@ namespace ts.server {
directory,
cb,
flags,
this.projectService.getWatchOptions(this),
WatchType.FailedLookupLocations,
this
);
@ -489,6 +491,7 @@ namespace ts.server {
directory,
cb,
flags,
this.projectService.getWatchOptions(this),
WatchType.TypeRoots,
this
);
@ -1170,6 +1173,7 @@ namespace ts.server {
}
},
PollingInterval.Medium,
this.projectService.getWatchOptions(this),
WatchType.MissingFile,
this
);
@ -1213,6 +1217,7 @@ namespace ts.server {
generatedFile,
() => this.projectService.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(this),
PollingInterval.High,
this.projectService.getWatchOptions(this),
WatchType.MissingGeneratedFile,
this
)
@ -1283,6 +1288,16 @@ namespace ts.server {
}
}
/*@internal*/
setWatchOptions(watchOptions: WatchOptions | undefined) {
this.watchOptions = watchOptions;
}
/*@internal*/
getWatchOptions(): WatchOptions | undefined {
return this.watchOptions;
}
/* @internal */
getChangesSinceVersion(lastKnownVersion?: number): ProjectFilesWithTSDiagnostics {
// Update the graph only if initial configured project load is not pending
@ -1516,6 +1531,7 @@ namespace ts.server {
}
},
PollingInterval.Low,
this.projectService.getWatchOptions(this),
WatchType.PackageJsonFile,
));
}
@ -1594,6 +1610,7 @@ namespace ts.server {
projectService: ProjectService,
documentRegistry: DocumentRegistry,
compilerOptions: CompilerOptions,
watchOptions: WatchOptions | undefined,
projectRootPath: NormalizedPath | undefined,
currentDirectory: string | undefined,
pluginConfigOverrides: Map<any> | undefined) {
@ -1606,6 +1623,7 @@ namespace ts.server {
/*lastFileExceededProgramSize*/ undefined,
compilerOptions,
/*compileOnSaveEnabled*/ false,
watchOptions,
projectService.host,
currentDirectory);
this.projectRootPath = projectRootPath && projectService.toCanonicalFileName(projectRootPath);
@ -1730,6 +1748,7 @@ namespace ts.server {
/*lastFileExceededProgramSize*/ undefined,
/*compilerOptions*/ {},
/*compileOnSaveEnabled*/ false,
/*watchOptions*/ undefined,
cachedDirectoryStructureHost,
getDirectoryPath(configFileName),
projectService.host.realpath && (s => this.getRealpath(s))
@ -1889,6 +1908,32 @@ namespace ts.server {
) || false;
}
/* @internal */
setWatchOptions(watchOptions: WatchOptions | undefined) {
const oldOptions = this.getWatchOptions();
super.setWatchOptions(watchOptions);
// If watch options different than older options
if (this.isInitialLoadPending() &&
!isJsonEqual(oldOptions, this.getWatchOptions())) {
const oldWatcher = this.configFileWatcher;
this.createConfigFileWatcher();
if (oldWatcher) oldWatcher.close();
}
}
/* @internal */
createConfigFileWatcher() {
this.configFileWatcher = this.projectService.watchFactory.watchFile(
this.projectService.host,
this.getConfigFilePath(),
(_fileName, eventKind) => this.projectService.onConfigChangedForConfiguredProject(this, eventKind),
PollingInterval.High,
this.projectService.getWatchOptions(this),
WatchType.ConfigFile,
this
);
}
/**
* If the project has reload from disk pending, it reloads (and then updates graph as part of that) instead of just updating the graph
* @returns: true if set of files in the project stays the same and false - otherwise.
@ -2111,7 +2156,8 @@ namespace ts.server {
lastFileExceededProgramSize: string | undefined,
public compileOnSaveEnabled: boolean,
projectFilePath?: string,
pluginConfigOverrides?: Map<any>) {
pluginConfigOverrides?: Map<any>,
watchOptions?: WatchOptions) {
super(externalProjectName,
ProjectKind.External,
projectService,
@ -2120,6 +2166,7 @@ namespace ts.server {
lastFileExceededProgramSize,
compilerOptions,
compileOnSaveEnabled,
watchOptions,
projectService.host,
getDirectoryPath(projectFilePath || normalizeSlashes(externalProjectName)));
this.enableGlobalPlugins(this.getCompilerOptions(), pluginConfigOverrides);

View file

@ -1276,7 +1276,7 @@ namespace ts.server.protocol {
* For external projects, some of the project settings are sent together with
* compiler settings.
*/
export type ExternalProjectCompilerOptions = CompilerOptions & CompileOnSaveMixin;
export type ExternalProjectCompilerOptions = CompilerOptions & CompileOnSaveMixin & WatchOptions;
/**
* Contains information about current project version
@ -1404,6 +1404,36 @@ namespace ts.server.protocol {
* The host's additional supported .js file extensions
*/
extraFileExtensions?: FileExtensionInfo[];
watchOptions?: WatchOptions;
}
export const enum WatchFileKind {
FixedPollingInterval = "FixedPollingInterval",
PriorityPollingInterval = "PriorityPollingInterval",
DynamicPriorityPolling = "DynamicPriorityPolling",
UseFsEvents = "UseFsEvents",
UseFsEventsOnParentDirectory = "UseFsEventsOnParentDirectory",
}
export const enum WatchDirectoryKind {
UseFsEvents = "UseFsEvents",
FixedPollingInterval = "FixedPollingInterval",
DynamicPriorityPolling = "DynamicPriorityPolling",
}
export const enum PollingWatchKind {
FixedInterval = "FixedInterval",
PriorityInterval = "PriorityInterval",
DynamicPriority = "DynamicPriority",
}
export interface WatchOptions {
watchFile?: WatchFileKind | ts.WatchFileKind;
watchDirectory?: WatchDirectoryKind | ts.WatchDirectoryKind;
fallbackPolling?: PollingWatchKind | ts.PollingWatchKind;
synchronousWatchDirectory?: boolean;
[option: string]: CompilerOptionsValue | undefined;
}
/**

View file

@ -7,8 +7,8 @@ declare namespace ts.server {
export type RequireResult = { module: {}, error: undefined } | { module: undefined, error: { stack?: string, message?: string } };
export interface ServerHost extends System {
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher;
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher;
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
clearTimeout(timeoutId: any): void;
setImmediate(callback: (...args: any[]) => void, ...args: any[]): any;

View file

@ -36,6 +36,7 @@ namespace ts.server {
projectName: project.getProjectName(),
fileNames: project.getFileNames(/*excludeFilesFromExternalLibraries*/ true, /*excludeConfigFiles*/ true).concat(project.getExcludedFiles() as NormalizedPath[]),
compilerOptions: project.getCompilationSettings(),
watchOptions: project.projectService.getWatchOptions(project),
typeAcquisition,
unresolvedImports,
projectRootPath: project.getCurrentDirectory() as Path,
@ -119,4 +120,4 @@ namespace ts.server {
export function createSortedArray<T>(): SortedArray<T> {
return [] as any as SortedArray<T>; // TODO: GH#19873
}
}
}

View file

@ -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",

View file

@ -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 }
});
});
});
});
}

View file

@ -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);
}
});

View 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 }
},
]);
});
});
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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();

View file

@ -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,

View file

@ -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));

View file

@ -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);

View file

@ -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");
});

View file

@ -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 }
}]
)
);
});
});
});
}

View file

@ -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);

View file

@ -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);
});
});
}

View file

@ -825,9 +825,9 @@ namespace ts.server {
const noopWatcher: FileWatcher = { close: noop };
// This is the function that catches the exceptions when watching directory, and yet lets project service continue to function
// Eg. on linux the number of watches are limited and one could easily exhaust watches and the exception ENOSPC is thrown when creating watcher at that point
function watchDirectorySwallowingException(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher {
function watchDirectorySwallowingException(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher {
try {
return originalWatchDirectory(path, callback, recursive);
return originalWatchDirectory(path, callback, recursive, options);
}
catch (e) {
logger.info(`Exception when creating directory watcher: ${e.message}`);
@ -838,7 +838,7 @@ namespace ts.server {
if (useWatchGuard) {
const currentDrive = extractWatchDirectoryCacheKey(sys.resolvePath(sys.getCurrentDirectory()), /*currentDriveKey*/ undefined);
const statusCache = createMap<boolean>();
sys.watchDirectory = (path, callback, recursive) => {
sys.watchDirectory = (path, callback, recursive, options) => {
const cacheKey = extractWatchDirectoryCacheKey(path, currentDrive);
let status = cacheKey && statusCache.get(cacheKey);
if (status === undefined) {
@ -871,7 +871,7 @@ namespace ts.server {
}
if (status) {
// this drive is safe to use - call real 'watchDirectory'
return watchDirectorySwallowingException(path, callback, recursive);
return watchDirectorySwallowingException(path, callback, recursive, options);
}
else {
// this drive is unsafe - return no-op watcher

View file

@ -171,7 +171,7 @@ namespace ts.server.typingsInstaller {
}
// start watching files
this.watchFiles(req.projectName, discoverTypingsResult.filesToWatch, req.projectRootPath);
this.watchFiles(req.projectName, discoverTypingsResult.filesToWatch, req.projectRootPath, req.watchOptions);
// install typings
if (discoverTypingsResult.newTypingNames.length) {
@ -399,7 +399,7 @@ namespace ts.server.typingsInstaller {
}
}
private watchFiles(projectName: string, files: string[], projectRootPath: Path) {
private watchFiles(projectName: string, files: string[], projectRootPath: Path, options: WatchOptions | undefined) {
if (!files.length) {
// shut down existing watchers
this.closeWatchers(projectName);
@ -439,7 +439,7 @@ namespace ts.server.typingsInstaller {
watchers.isInvoked = true;
this.sendResponse({ projectName, kind: ActionInvalidate });
}
}, /*pollingInterval*/ 2000) :
}, /*pollingInterval*/ 2000, options) :
this.installTypingHost.watchDirectory!(path, f => { // TODO: GH#18217
if (isLoggingEnabled) {
this.log.writeLine(`DirectoryWatcher:: Triggered with ${f} :: WatchInfo: ${path} recursive :: handler is already invoked '${watchers.isInvoked}'`);
@ -453,7 +453,7 @@ namespace ts.server.typingsInstaller {
watchers.isInvoked = true;
this.sendResponse({ projectName, kind: ActionInvalidate });
}
}, /*recursive*/ true);
}, /*recursive*/ true, options);
watchers.set(canonicalPath, isLoggingEnabled ? {
close: () => {

View file

@ -2576,6 +2576,23 @@ declare namespace ts {
/** True if it is intended that this reference form a circularity */
circular?: boolean;
}
export enum WatchFileKind {
FixedPollingInterval = 0,
PriorityPollingInterval = 1,
DynamicPriorityPolling = 2,
UseFsEvents = 3,
UseFsEventsOnParentDirectory = 4
}
export enum WatchDirectoryKind {
UseFsEvents = 0,
FixedPollingInterval = 1,
DynamicPriorityPolling = 2
}
export enum PollingWatchKind {
FixedInterval = 0,
PriorityInterval = 1,
DynamicPriority = 2
}
export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[] | ProjectReference[] | null | undefined;
export interface CompilerOptions {
allowJs?: boolean;
@ -2663,6 +2680,13 @@ declare namespace ts {
useDefineForClassFields?: boolean;
[option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined;
}
export interface WatchOptions {
watchFile?: WatchFileKind;
watchDirectory?: WatchDirectoryKind;
fallbackPolling?: PollingWatchKind;
synchronousWatchDirectory?: boolean;
[option: string]: CompilerOptionsValue | undefined;
}
export interface TypeAcquisition {
/**
* @deprecated typingOptions.enableAutoDiscovery
@ -2735,6 +2759,7 @@ declare namespace ts {
typeAcquisition?: TypeAcquisition;
fileNames: string[];
projectReferences?: readonly ProjectReference[];
watchOptions?: WatchOptions;
raw?: any;
errors: Diagnostic[];
wildcardDirectories?: MapLike<WatchDirectoryFlags>;
@ -3211,8 +3236,8 @@ declare namespace ts {
* @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that
* use native OS file watching
*/
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher;
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher;
resolvePath(path: string): string;
fileExists(path: string): boolean;
directoryExists(path: string): boolean;
@ -3739,7 +3764,7 @@ declare namespace ts {
/**
* Reads the config file, reports errors if any and exits if the config file cannot be found
*/
export function getParsedCommandLineOfConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost, extendedConfigCache?: Map<ExtendedConfigCacheEntry>): ParsedCommandLine | undefined;
export function getParsedCommandLineOfConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost, extendedConfigCache?: Map<ExtendedConfigCacheEntry>, watchOptionsToExtend?: WatchOptions): ParsedCommandLine | undefined;
/**
* Read tsconfig.json file
* @param fileName The path to the config file
@ -3773,7 +3798,7 @@ declare namespace ts {
* @param basePath A root directory to resolve relative path entries in the config
* file to. e.g. outDir
*/
export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map<ExtendedConfigCacheEntry>): ParsedCommandLine;
export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map<ExtendedConfigCacheEntry>, existingWatchOptions?: WatchOptions): ParsedCommandLine;
/**
* Parse the contents of a config file (tsconfig.json).
* @param jsonNode The contents of the config file to parse
@ -3781,10 +3806,11 @@ declare namespace ts {
* @param basePath A root directory to resolve relative path entries in the config
* file to. e.g. outDir
*/
export function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map<ExtendedConfigCacheEntry>): ParsedCommandLine;
export function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map<ExtendedConfigCacheEntry>, existingWatchOptions?: WatchOptions): ParsedCommandLine;
export interface ParsedTsconfig {
raw: any;
options?: CompilerOptions;
watchOptions?: WatchOptions;
typeAcquisition?: TypeAcquisition;
/**
* Note that the case of the config path has not yet been normalized, as no files have been imported into the project yet
@ -4585,9 +4611,9 @@ declare namespace ts {
/** If provided, called with Diagnostic message that informs about change in watch status */
onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions, errorCount?: number): void;
/** Used to watch changes in source files, missing files needed to update the program or config file */
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: CompilerOptions): FileWatcher;
/** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: CompilerOptions): FileWatcher;
/** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */
setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
/** If provided, will be used to reset existing delayed compilation */
@ -4643,6 +4669,7 @@ declare namespace ts {
rootFiles: string[];
/** Compiler options */
options: CompilerOptions;
watchOptions?: WatchOptions;
/** Project References */
projectReferences?: readonly ProjectReference[];
}
@ -4654,6 +4681,7 @@ declare namespace ts {
configFileName: string;
/** Options to extend */
optionsToExtend?: CompilerOptions;
watchOptionsToExtend?: WatchOptions;
/**
* Used to generate source file names from the config file and its include, exclude, files rules
* and also to cache the directory stucture
@ -4681,8 +4709,8 @@ declare namespace ts {
/**
* Create the watch compiler host for either configFile or fileNames and its options
*/
function createWatchCompilerHost<T extends BuilderProgram>(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHostOfConfigFile<T>;
function createWatchCompilerHost<T extends BuilderProgram>(rootFiles: string[], options: CompilerOptions, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: readonly ProjectReference[]): WatchCompilerHostOfFilesAndCompilerOptions<T>;
function createWatchCompilerHost<T extends BuilderProgram>(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, watchOptionsToExtend?: WatchOptions): WatchCompilerHostOfConfigFile<T>;
function createWatchCompilerHost<T extends BuilderProgram>(rootFiles: string[], options: CompilerOptions, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: readonly ProjectReference[], watchOptions?: WatchOptions): WatchCompilerHostOfFilesAndCompilerOptions<T>;
/**
* Creates the watch from the host for root files and compiler options
*/
@ -4736,7 +4764,7 @@ declare namespace ts {
function createSolutionBuilderHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system?: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary): SolutionBuilderHost<T>;
function createSolutionBuilderWithWatchHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system?: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): SolutionBuilderWithWatchHost<T>;
function createSolutionBuilder<T extends BuilderProgram>(host: SolutionBuilderHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder<T>;
function createSolutionBuilderWithWatch<T extends BuilderProgram>(host: SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder<T>;
function createSolutionBuilderWithWatch<T extends BuilderProgram>(host: SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions, baseWatchOptions?: WatchOptions): SolutionBuilder<T>;
enum InvalidatedProjectKind {
Build = 0,
UpdateBundle = 1,
@ -4797,6 +4825,7 @@ declare namespace ts.server {
readonly fileNames: string[];
readonly projectRootPath: Path;
readonly compilerOptions: CompilerOptions;
readonly watchOptions?: WatchOptions;
readonly typeAcquisition: TypeAcquisition;
readonly unresolvedImports: SortedReadonlyArray<string>;
readonly cachePath?: string;
@ -5913,8 +5942,8 @@ declare namespace ts.server {
};
};
interface ServerHost extends System {
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher;
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher;
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
clearTimeout(timeoutId: any): void;
setImmediate(callback: (...args: any[]) => void, ...args: any[]): any;
@ -6934,7 +6963,7 @@ declare namespace ts.server.protocol {
* For external projects, some of the project settings are sent together with
* compiler settings.
*/
type ExternalProjectCompilerOptions = CompilerOptions & CompileOnSaveMixin;
type ExternalProjectCompilerOptions = CompilerOptions & CompileOnSaveMixin & WatchOptions;
/**
* Represents a set of changes that happen in project
*/
@ -6974,6 +7003,31 @@ declare namespace ts.server.protocol {
* The host's additional supported .js file extensions
*/
extraFileExtensions?: FileExtensionInfo[];
watchOptions?: WatchOptions;
}
enum WatchFileKind {
FixedPollingInterval = "FixedPollingInterval",
PriorityPollingInterval = "PriorityPollingInterval",
DynamicPriorityPolling = "DynamicPriorityPolling",
UseFsEvents = "UseFsEvents",
UseFsEventsOnParentDirectory = "UseFsEventsOnParentDirectory"
}
enum WatchDirectoryKind {
UseFsEvents = "UseFsEvents",
FixedPollingInterval = "FixedPollingInterval",
DynamicPriorityPolling = "DynamicPriorityPolling"
}
enum PollingWatchKind {
FixedInterval = "FixedInterval",
PriorityInterval = "PriorityInterval",
DynamicPriority = "DynamicPriority"
}
interface WatchOptions {
watchFile?: WatchFileKind | ts.WatchFileKind;
watchDirectory?: WatchDirectoryKind | ts.WatchDirectoryKind;
fallbackPolling?: PollingWatchKind | ts.PollingWatchKind;
synchronousWatchDirectory?: boolean;
[option: string]: CompilerOptionsValue | undefined;
}
/**
* Configure request; value of command field is "configure". Specifies
@ -8518,6 +8572,7 @@ declare namespace ts.server {
private documentRegistry;
private compilerOptions;
compileOnSaveEnabled: boolean;
protected watchOptions: WatchOptions | undefined;
private rootFiles;
private rootFilesMap;
private program;
@ -8874,6 +8929,7 @@ declare namespace ts.server {
}
export function convertFormatOptions(protocolOptions: protocol.FormatCodeSettings): FormatCodeSettings;
export function convertCompilerOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): CompilerOptions & protocol.CompileOnSaveMixin;
export function convertWatchOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): WatchOptions | undefined;
export function tryConvertScriptKindName(scriptKindName: protocol.ScriptKindName | ScriptKind): ScriptKind;
export function convertScriptKindName(scriptKindName: protocol.ScriptKindName): ScriptKind.Unknown | ScriptKind.JS | ScriptKind.JSX | ScriptKind.TS | ScriptKind.TSX;
export interface HostConfiguration {
@ -8881,6 +8937,7 @@ declare namespace ts.server {
preferences: protocol.UserPreferences;
hostInfo: string;
extraFileExtensions?: FileExtensionInfo[];
watchOptions?: WatchOptions;
}
export interface OpenConfiguredProjectResult {
configFileName?: NormalizedPath;
@ -8937,6 +8994,8 @@ declare namespace ts.server {
private readonly openFilesWithNonRootedDiskPath;
private compilerOptionsForInferredProjects;
private compilerOptionsForInferredProjectsPerProjectRoot;
private watchOptionsForInferredProjects;
private watchOptionsForInferredProjectsPerProjectRoot;
/**
* Project size for configured or external projects
*/
@ -9003,7 +9062,6 @@ declare namespace ts.server {
private delayUpdateSourceInfoProjects;
private delayUpdateProjectsOfScriptInfoPath;
private handleDeletedFile;
private onConfigChangedForConfiguredProject;
/**
* This is the callback function for the config file add/remove/change at any location
* that matters to open script info but doesnt have configured project open

View file

@ -2576,6 +2576,23 @@ declare namespace ts {
/** True if it is intended that this reference form a circularity */
circular?: boolean;
}
export enum WatchFileKind {
FixedPollingInterval = 0,
PriorityPollingInterval = 1,
DynamicPriorityPolling = 2,
UseFsEvents = 3,
UseFsEventsOnParentDirectory = 4
}
export enum WatchDirectoryKind {
UseFsEvents = 0,
FixedPollingInterval = 1,
DynamicPriorityPolling = 2
}
export enum PollingWatchKind {
FixedInterval = 0,
PriorityInterval = 1,
DynamicPriority = 2
}
export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[] | ProjectReference[] | null | undefined;
export interface CompilerOptions {
allowJs?: boolean;
@ -2663,6 +2680,13 @@ declare namespace ts {
useDefineForClassFields?: boolean;
[option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined;
}
export interface WatchOptions {
watchFile?: WatchFileKind;
watchDirectory?: WatchDirectoryKind;
fallbackPolling?: PollingWatchKind;
synchronousWatchDirectory?: boolean;
[option: string]: CompilerOptionsValue | undefined;
}
export interface TypeAcquisition {
/**
* @deprecated typingOptions.enableAutoDiscovery
@ -2735,6 +2759,7 @@ declare namespace ts {
typeAcquisition?: TypeAcquisition;
fileNames: string[];
projectReferences?: readonly ProjectReference[];
watchOptions?: WatchOptions;
raw?: any;
errors: Diagnostic[];
wildcardDirectories?: MapLike<WatchDirectoryFlags>;
@ -3211,8 +3236,8 @@ declare namespace ts {
* @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that
* use native OS file watching
*/
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher;
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher;
resolvePath(path: string): string;
fileExists(path: string): boolean;
directoryExists(path: string): boolean;
@ -3739,7 +3764,7 @@ declare namespace ts {
/**
* Reads the config file, reports errors if any and exits if the config file cannot be found
*/
export function getParsedCommandLineOfConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost, extendedConfigCache?: Map<ExtendedConfigCacheEntry>): ParsedCommandLine | undefined;
export function getParsedCommandLineOfConfigFile(configFileName: string, optionsToExtend: CompilerOptions, host: ParseConfigFileHost, extendedConfigCache?: Map<ExtendedConfigCacheEntry>, watchOptionsToExtend?: WatchOptions): ParsedCommandLine | undefined;
/**
* Read tsconfig.json file
* @param fileName The path to the config file
@ -3773,7 +3798,7 @@ declare namespace ts {
* @param basePath A root directory to resolve relative path entries in the config
* file to. e.g. outDir
*/
export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map<ExtendedConfigCacheEntry>): ParsedCommandLine;
export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map<ExtendedConfigCacheEntry>, existingWatchOptions?: WatchOptions): ParsedCommandLine;
/**
* Parse the contents of a config file (tsconfig.json).
* @param jsonNode The contents of the config file to parse
@ -3781,10 +3806,11 @@ declare namespace ts {
* @param basePath A root directory to resolve relative path entries in the config
* file to. e.g. outDir
*/
export function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map<ExtendedConfigCacheEntry>): ParsedCommandLine;
export function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map<ExtendedConfigCacheEntry>, existingWatchOptions?: WatchOptions): ParsedCommandLine;
export interface ParsedTsconfig {
raw: any;
options?: CompilerOptions;
watchOptions?: WatchOptions;
typeAcquisition?: TypeAcquisition;
/**
* Note that the case of the config path has not yet been normalized, as no files have been imported into the project yet
@ -4585,9 +4611,9 @@ declare namespace ts {
/** If provided, called with Diagnostic message that informs about change in watch status */
onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions, errorCount?: number): void;
/** Used to watch changes in source files, missing files needed to update the program or config file */
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: CompilerOptions): FileWatcher;
/** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: CompilerOptions): FileWatcher;
/** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */
setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
/** If provided, will be used to reset existing delayed compilation */
@ -4643,6 +4669,7 @@ declare namespace ts {
rootFiles: string[];
/** Compiler options */
options: CompilerOptions;
watchOptions?: WatchOptions;
/** Project References */
projectReferences?: readonly ProjectReference[];
}
@ -4654,6 +4681,7 @@ declare namespace ts {
configFileName: string;
/** Options to extend */
optionsToExtend?: CompilerOptions;
watchOptionsToExtend?: WatchOptions;
/**
* Used to generate source file names from the config file and its include, exclude, files rules
* and also to cache the directory stucture
@ -4681,8 +4709,8 @@ declare namespace ts {
/**
* Create the watch compiler host for either configFile or fileNames and its options
*/
function createWatchCompilerHost<T extends BuilderProgram>(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHostOfConfigFile<T>;
function createWatchCompilerHost<T extends BuilderProgram>(rootFiles: string[], options: CompilerOptions, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: readonly ProjectReference[]): WatchCompilerHostOfFilesAndCompilerOptions<T>;
function createWatchCompilerHost<T extends BuilderProgram>(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, watchOptionsToExtend?: WatchOptions): WatchCompilerHostOfConfigFile<T>;
function createWatchCompilerHost<T extends BuilderProgram>(rootFiles: string[], options: CompilerOptions, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: readonly ProjectReference[], watchOptions?: WatchOptions): WatchCompilerHostOfFilesAndCompilerOptions<T>;
/**
* Creates the watch from the host for root files and compiler options
*/
@ -4736,7 +4764,7 @@ declare namespace ts {
function createSolutionBuilderHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system?: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary): SolutionBuilderHost<T>;
function createSolutionBuilderWithWatchHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system?: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): SolutionBuilderWithWatchHost<T>;
function createSolutionBuilder<T extends BuilderProgram>(host: SolutionBuilderHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder<T>;
function createSolutionBuilderWithWatch<T extends BuilderProgram>(host: SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder<T>;
function createSolutionBuilderWithWatch<T extends BuilderProgram>(host: SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions, baseWatchOptions?: WatchOptions): SolutionBuilder<T>;
enum InvalidatedProjectKind {
Build = 0,
UpdateBundle = 1,
@ -4797,6 +4825,7 @@ declare namespace ts.server {
readonly fileNames: string[];
readonly projectRootPath: Path;
readonly compilerOptions: CompilerOptions;
readonly watchOptions?: WatchOptions;
readonly typeAcquisition: TypeAcquisition;
readonly unresolvedImports: SortedReadonlyArray<string>;
readonly cachePath?: string;

View file

@ -0,0 +1,9 @@
{
"compilerOptions": {},
"watchOptions": {
"watchFile": "dynamicprioritypolling"
},
"include": [
"./src/**/*"
]
}

View file

@ -0,0 +1,6 @@
{
"compilerOptions": {},
"watchOptions": {
"fallbackPolling": "fixedinterval"
}
}

View file

@ -0,0 +1,6 @@
{
"compilerOptions": {},
"watchOptions": {
"synchronousWatchDirectory": true
}
}

View file

@ -0,0 +1,6 @@
{
"compilerOptions": {},
"watchOptions": {
"watchDirectory": "usefsevents"
}
}

View file

@ -0,0 +1,6 @@
{
"compilerOptions": {},
"watchOptions": {
"watchFile": "fixedpollinginterval"
}
}