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
40 changed files with 2417 additions and 720 deletions
+2 -2
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);
}
}
}
}
+305 -152
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 {
+28
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",
+479 -210
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;
+17 -13
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),
+33 -2
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 */
+6 -4
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;
}
+22 -17
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
);
}
+50 -37
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) {