Skip to content

Commit 985a9b6

Browse files
committed
Support passing watch options from command line
1 parent 909f458 commit 985a9b6

19 files changed

+426
-163
lines changed

src/compiler/commandLineParser.ts

Lines changed: 88 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,12 +1156,14 @@ namespace ts {
11561156
commandLine: readonly string[],
11571157
readFile?: (path: string) => string | undefined) {
11581158
const options = {} as OptionsBase;
1159+
let watchOptions: WatchOptions | undefined;
11591160
const fileNames: string[] = [];
11601161
const errors: Diagnostic[] = [];
11611162

11621163
parseStrings(commandLine);
11631164
return {
11641165
options,
1166+
watchOptions,
11651167
fileNames,
11661168
errors
11671169
};
@@ -1178,51 +1180,17 @@ namespace ts {
11781180
const inputOptionName = s.slice(s.charCodeAt(1) === CharacterCodes.minus ? 2 : 1);
11791181
const opt = getOptionDeclarationFromName(diagnostics.getOptionNameMap, inputOptionName, /*allowShort*/ true);
11801182
if (opt) {
1181-
if (opt.isTSConfigOnly) {
1182-
errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file, opt.name));
1183+
i = parseOptionValue(args, i, diagnostics, opt, options, errors);
1184+
}
1185+
else {
1186+
const watchOpt = getOptionDeclarationFromName(watchOptionsDidYouMeanDiagnostics.getOptionNameMap, inputOptionName, /*allowShort*/ true);
1187+
if (watchOpt) {
1188+
i = parseOptionValue(args, i, watchOptionsDidYouMeanDiagnostics, watchOpt, watchOptions || (watchOptions = {}), errors);
11831189
}
11841190
else {
1185-
// Check to see if no argument was provided (e.g. "--locale" is the last command-line argument).
1186-
if (!args[i] && opt.type !== "boolean") {
1187-
errors.push(createCompilerDiagnostic(diagnostics.optionTypeMismatchDiagnostic, opt.name));
1188-
}
1189-
1190-
switch (opt.type) {
1191-
case "number":
1192-
options[opt.name] = parseInt(args[i]);
1193-
i++;
1194-
break;
1195-
case "boolean":
1196-
// boolean flag has optional value true, false, others
1197-
const optValue = args[i];
1198-
options[opt.name] = optValue !== "false";
1199-
// consume next argument as boolean flag value
1200-
if (optValue === "false" || optValue === "true") {
1201-
i++;
1202-
}
1203-
break;
1204-
case "string":
1205-
options[opt.name] = args[i] || "";
1206-
i++;
1207-
break;
1208-
case "list":
1209-
const result = parseListTypeOption(opt, args[i], errors);
1210-
options[opt.name] = result || [];
1211-
if (result) {
1212-
i++;
1213-
}
1214-
break;
1215-
// If not a primitive, the possible types are specified in what is effectively a map of options.
1216-
default:
1217-
options[opt.name] = parseCustomTypeOption(<CommandLineOptionOfCustomType>opt, args[i], errors);
1218-
i++;
1219-
break;
1220-
}
1191+
errors.push(createUnknownOptionError(inputOptionName, diagnostics, createCompilerDiagnostic, s));
12211192
}
12221193
}
1223-
else {
1224-
errors.push(createUnknownOptionError(inputOptionName, diagnostics, createCompilerDiagnostic, s));
1225-
}
12261194
}
12271195
else {
12281196
fileNames.push(s);
@@ -1264,6 +1232,58 @@ namespace ts {
12641232
}
12651233
}
12661234

1235+
function parseOptionValue(
1236+
args: readonly string[],
1237+
i: number,
1238+
diagnostics: ParseCommandLineWorkerDiagnostics,
1239+
opt: CommandLineOption,
1240+
options: OptionsBase,
1241+
errors: Diagnostic[]
1242+
) {
1243+
if (opt.isTSConfigOnly) {
1244+
errors.push(createCompilerDiagnostic(Diagnostics.Option_0_can_only_be_specified_in_tsconfig_json_file, opt.name));
1245+
}
1246+
else {
1247+
// Check to see if no argument was provided (e.g. "--locale" is the last command-line argument).
1248+
if (!args[i] && opt.type !== "boolean") {
1249+
errors.push(createCompilerDiagnostic(diagnostics.optionTypeMismatchDiagnostic, opt.name, getCompilerOptionValueTypeString(opt)));
1250+
}
1251+
1252+
switch (opt.type) {
1253+
case "number":
1254+
options[opt.name] = parseInt(args[i]);
1255+
i++;
1256+
break;
1257+
case "boolean":
1258+
// boolean flag has optional value true, false, others
1259+
const optValue = args[i];
1260+
options[opt.name] = optValue !== "false";
1261+
// consume next argument as boolean flag value
1262+
if (optValue === "false" || optValue === "true") {
1263+
i++;
1264+
}
1265+
break;
1266+
case "string":
1267+
options[opt.name] = args[i] || "";
1268+
i++;
1269+
break;
1270+
case "list":
1271+
const result = parseListTypeOption(opt, args[i], errors);
1272+
options[opt.name] = result || [];
1273+
if (result) {
1274+
i++;
1275+
}
1276+
break;
1277+
// If not a primitive, the possible types are specified in what is effectively a map of options.
1278+
default:
1279+
options[opt.name] = parseCustomTypeOption(<CommandLineOptionOfCustomType>opt, args[i], errors);
1280+
i++;
1281+
break;
1282+
}
1283+
}
1284+
return i;
1285+
}
1286+
12671287
const compilerOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = {
12681288
getOptionNameMap,
12691289
optionDeclarations,
@@ -1296,6 +1316,7 @@ namespace ts {
12961316
/*@internal*/
12971317
export interface ParsedBuildCommand {
12981318
buildOptions: BuildOptions;
1319+
watchOptions: WatchOptions | undefined;
12991320
projects: string[];
13001321
errors: Diagnostic[];
13011322
}
@@ -1315,7 +1336,7 @@ namespace ts {
13151336

13161337
/*@internal*/
13171338
export function parseBuildCommand(args: readonly string[]): ParsedBuildCommand {
1318-
const { options, fileNames: projects, errors } = parseCommandLineWorker(
1339+
const { options, watchOptions, fileNames: projects, errors } = parseCommandLineWorker(
13191340
buildOptionsDidYouMeanDiagnostics,
13201341
args
13211342
);
@@ -1340,7 +1361,7 @@ namespace ts {
13401361
errors.push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "watch", "dry"));
13411362
}
13421363

1343-
return { buildOptions, projects, errors };
1364+
return { buildOptions, watchOptions, projects, errors };
13441365
}
13451366

13461367
/* @internal */
@@ -1374,7 +1395,8 @@ namespace ts {
13741395
configFileName: string,
13751396
optionsToExtend: CompilerOptions,
13761397
host: ParseConfigFileHost,
1377-
extendedConfigCache?: Map<ExtendedConfigCacheEntry>
1398+
extendedConfigCache?: Map<ExtendedConfigCacheEntry>,
1399+
watchOptionsToExtend?: WatchOptions
13781400
): ParsedCommandLine | undefined {
13791401
let configFileText: string | undefined;
13801402
try {
@@ -1404,7 +1426,8 @@ namespace ts {
14041426
getNormalizedAbsolutePath(configFileName, cwd),
14051427
/*resolutionStack*/ undefined,
14061428
/*extraFileExtension*/ undefined,
1407-
extendedConfigCache
1429+
extendedConfigCache,
1430+
watchOptionsToExtend
14081431
);
14091432
}
14101433

@@ -1460,10 +1483,17 @@ namespace ts {
14601483
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1,
14611484
};
14621485

1463-
const watchOptionsDidYouMeanDiagnostics: DidYouMeanOptionsDiagnostics = {
1486+
1487+
let watchOptionNameMapCache: OptionNameMap;
1488+
function getWatchOptionNameMap(): OptionNameMap {
1489+
return watchOptionNameMapCache || (watchOptionNameMapCache = createOptionNameMap(optionsForWatch));
1490+
}
1491+
const watchOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = {
1492+
getOptionNameMap: getWatchOptionNameMap,
14641493
optionDeclarations: optionsForWatch,
14651494
unknownOptionDiagnostic: Diagnostics.Unknown_watch_option_0,
14661495
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_watch_option_0_Did_you_mean_1,
1496+
optionTypeMismatchDiagnostic: Diagnostics.Watch_option_0_requires_a_value_of_type_1
14671497
};
14681498

14691499
let _tsconfigRootOptions: TsConfigOnlyOption;
@@ -1826,7 +1856,7 @@ namespace ts {
18261856
)
18271857
),
18281858
f => getRelativePathFromFile(getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), getNormalizedAbsolutePath(f, host.getCurrentDirectory()), getCanonicalFileName)
1829-
); //TODO
1859+
); // TODO
18301860
const optionMap = serializeCompilerOptions(configParseResult.options, { configFilePath: getNormalizedAbsolutePath(configFileName, host.getCurrentDirectory()), useCaseSensitiveFileNames: host.useCaseSensitiveFileNames });
18311861
const config = {
18321862
compilerOptions: {
@@ -2089,8 +2119,8 @@ namespace ts {
20892119
* @param basePath A root directory to resolve relative path entries in the config
20902120
* file to. e.g. outDir
20912121
*/
2092-
export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map<ExtendedConfigCacheEntry>): ParsedCommandLine {
2093-
return parseJsonConfigFileContentWorker(json, /*sourceFile*/ undefined, host, basePath, existingOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache);
2122+
export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map<ExtendedConfigCacheEntry>, existingWatchOptions?: WatchOptions): ParsedCommandLine {
2123+
return parseJsonConfigFileContentWorker(json, /*sourceFile*/ undefined, host, basePath, existingOptions, existingWatchOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache);
20942124
}
20952125

20962126
/**
@@ -2100,8 +2130,8 @@ namespace ts {
21002130
* @param basePath A root directory to resolve relative path entries in the config
21012131
* file to. e.g. outDir
21022132
*/
2103-
export function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map<ExtendedConfigCacheEntry>): ParsedCommandLine {
2104-
return parseJsonConfigFileContentWorker(/*json*/ undefined, sourceFile, host, basePath, existingOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache);
2133+
export function parseJsonSourceFileConfigFileContent(sourceFile: TsConfigSourceFile, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string, resolutionStack?: Path[], extraFileExtensions?: readonly FileExtensionInfo[], extendedConfigCache?: Map<ExtendedConfigCacheEntry>, existingWatchOptions?: WatchOptions): ParsedCommandLine {
2134+
return parseJsonConfigFileContentWorker(/*json*/ undefined, sourceFile, host, basePath, existingOptions, existingWatchOptions, configFileName, resolutionStack, extraFileExtensions, extendedConfigCache);
21052135
}
21062136

21072137
/*@internal*/
@@ -2136,24 +2166,29 @@ namespace ts {
21362166
host: ParseConfigHost,
21372167
basePath: string,
21382168
existingOptions: CompilerOptions = {},
2169+
existingWatchOptions: WatchOptions | undefined,
21392170
configFileName?: string,
21402171
resolutionStack: Path[] = [],
21412172
extraFileExtensions: readonly FileExtensionInfo[] = [],
21422173
extendedConfigCache?: Map<ExtendedConfigCacheEntry>
2143-
): ParsedCommandLine { // TODO -- pass options to extend?
2174+
): ParsedCommandLine {
21442175
Debug.assert((json === undefined && sourceFile !== undefined) || (json !== undefined && sourceFile === undefined));
21452176
const errors: Diagnostic[] = [];
21462177

21472178
const parsedConfig = parseConfig(json, sourceFile, host, basePath, configFileName, resolutionStack, errors, extendedConfigCache);
21482179
const { raw } = parsedConfig;
21492180
const options = extend(existingOptions, parsedConfig.options || {});
2181+
const watchOptions = existingWatchOptions && parsedConfig.watchOptions ?
2182+
extend(existingWatchOptions, parsedConfig.watchOptions) :
2183+
parsedConfig.watchOptions || existingWatchOptions;
2184+
21502185
options.configFilePath = configFileName && normalizeSlashes(configFileName);
21512186
setConfigFileInOptions(options, sourceFile);
21522187
let projectReferences: ProjectReference[] | undefined;
21532188
const { fileNames, wildcardDirectories, spec } = getFileNames();
21542189
return {
21552190
options,
2156-
watchOptions: parsedConfig.watchOptions,
2191+
watchOptions,
21572192
fileNames,
21582193
projectReferences,
21592194
typeAcquisition: parsedConfig.typeAcquisition || getDefaultTypeAcquisition(),

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3317,6 +3317,10 @@
33173317
"category": "Error",
33183318
"code": 5079
33193319
},
3320+
"Watch option '{0}' requires a value of type {1}.": {
3321+
"category": "Error",
3322+
"code": 5080
3323+
},
33203324

33213325
"Generates a sourcemap for each corresponding '.d.ts' file.": {
33223326
"category": "Message",

src/compiler/tsbuildPublic.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,8 @@ namespace ts {
205205
return createSolutionBuilderWorker(/*watch*/ false, host, rootNames, defaultOptions);
206206
}
207207

208-
export function createSolutionBuilderWithWatch<T extends BuilderProgram>(host: SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder<T> {
209-
return createSolutionBuilderWorker(/*watch*/ true, host, rootNames, defaultOptions);
208+
export function createSolutionBuilderWithWatch<T extends BuilderProgram>(host: SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions, baseWatchOptions?: WatchOptions): SolutionBuilder<T> {
209+
return createSolutionBuilderWorker(/*watch*/ true, host, rootNames, defaultOptions, baseWatchOptions);
210210
}
211211

212212
type ConfigFileCacheEntry = ParsedCommandLine | Diagnostic;
@@ -232,6 +232,7 @@ namespace ts {
232232
readonly options: BuildOptions;
233233
readonly baseCompilerOptions: CompilerOptions;
234234
readonly rootNames: readonly string[];
235+
readonly baseWatchOptions: WatchOptions | undefined;
235236

236237
readonly resolvedConfigFilePaths: Map<ResolvedConfigFilePath>;
237238
readonly configFileCache: ConfigFileMap<ConfigFileCacheEntry>;
@@ -272,7 +273,7 @@ namespace ts {
272273
writeLog: (s: string) => void;
273274
}
274275

275-
function createSolutionBuilderState<T extends BuilderProgram>(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], options: BuildOptions): SolutionBuilderState<T> {
276+
function createSolutionBuilderState<T extends BuilderProgram>(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], options: BuildOptions, baseWatchOptions: WatchOptions | undefined): SolutionBuilderState<T> {
276277
const host = hostOrHostWithWatch as SolutionBuilderHost<T>;
277278
const hostWithWatch = hostOrHostWithWatch as SolutionBuilderWithWatchHost<T>;
278279
const currentDirectory = host.getCurrentDirectory();
@@ -306,6 +307,7 @@ namespace ts {
306307
options,
307308
baseCompilerOptions,
308309
rootNames,
310+
baseWatchOptions,
309311

310312
resolvedConfigFilePaths: createMap(),
311313
configFileCache: createConfigFileMap(),
@@ -374,15 +376,15 @@ namespace ts {
374376
}
375377

376378
let diagnostic: Diagnostic | undefined;
377-
const { parseConfigFileHost, baseCompilerOptions, extendedConfigCache, host } = state;
379+
const { parseConfigFileHost, baseCompilerOptions, baseWatchOptions, extendedConfigCache, host } = state;
378380
let parsed: ParsedCommandLine | undefined;
379381
if (host.getParsedCommandLine) {
380382
parsed = host.getParsedCommandLine(configFileName);
381383
if (!parsed) diagnostic = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName);
382384
}
383385
else {
384386
parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = d => diagnostic = d;
385-
parsed = getParsedCommandLineOfConfigFile(configFileName, baseCompilerOptions, parseConfigFileHost, extendedConfigCache);
387+
parsed = getParsedCommandLineOfConfigFile(configFileName, baseCompilerOptions, parseConfigFileHost, extendedConfigCache, baseWatchOptions);
386388
parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = noop;
387389
}
388390
configFileCache.set(configFilePath, parsed || diagnostic!);
@@ -1872,9 +1874,9 @@ namespace ts {
18721874
* can dynamically add/remove other projects based on changes on the rootNames' references
18731875
*/
18741876
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: false, host: SolutionBuilderHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder<T>;
1875-
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: true, host: SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions): SolutionBuilder<T>;
1876-
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], options: BuildOptions): SolutionBuilder<T> {
1877-
const state = createSolutionBuilderState(watch, hostOrHostWithWatch, rootNames, options);
1877+
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: true, host: SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], defaultOptions: BuildOptions, baseWatchOptions?: WatchOptions): SolutionBuilder<T>;
1878+
function createSolutionBuilderWorker<T extends BuilderProgram>(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost<T> | SolutionBuilderWithWatchHost<T>, rootNames: readonly string[], options: BuildOptions, baseWatchOptions?: WatchOptions): SolutionBuilder<T> {
1879+
const state = createSolutionBuilderState(watch, hostOrHostWithWatch, rootNames, options, baseWatchOptions);
18781880
return {
18791881
build: (project, cancellationToken) => build(state, project, cancellationToken),
18801882
clean: project => clean(state, project),

0 commit comments

Comments
 (0)