Skip to content

Commit 4ca063f

Browse files
committed
Allow watchFactory as a object with name and configuration
1 parent 8b31670 commit 4ca063f

File tree

50 files changed

+1030
-666
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1030
-666
lines changed

src/compiler/commandLineParser.ts

Lines changed: 75 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
CommandLineOption,
1313
CommandLineOptionOfCustomType,
1414
CommandLineOptionOfListType,
15+
CommandLineOptionOfObjectType,
1516
CompilerOptions,
1617
CompilerOptionsValue,
1718
ConfigFileSpecs,
@@ -22,6 +23,7 @@ import {
2223
createGetCanonicalFileName,
2324
Debug,
2425
Diagnostic,
26+
DiagnosticAndArguments,
2527
DiagnosticArguments,
2628
DiagnosticMessage,
2729
Diagnostics,
@@ -45,6 +47,7 @@ import {
4547
flatten,
4648
forEach,
4749
forEachEntry,
50+
forEachPropertyAssignment,
4851
forEachTsConfigPropArray,
4952
getBaseFileName,
5053
getDirectoryPath,
@@ -95,6 +98,7 @@ import {
9598
parseJsonText,
9699
parsePackageName,
97100
Path,
101+
PluginImport,
98102
PollingWatchKind,
99103
PrefixUnaryExpression,
100104
ProjectReference,
@@ -112,7 +116,6 @@ import {
112116
toPath,
113117
tracing,
114118
trimString,
115-
TsConfigOnlyOption,
116119
TsConfigSourceFile,
117120
TypeAcquisition,
118121
unescapeLeadingUnderscores,
@@ -315,17 +318,27 @@ export const optionsForWatch: CommandLineOption[] = [
315318
},
316319
{
317320
name: "watchFactory",
318-
type: "string",
321+
type: "string | object",
319322
category: Diagnostics.Watch_and_Build_Modes,
320323
description: Diagnostics.Specify_which_factory_to_invoke_watchFile_and_watchDirectory_on,
321-
extraValidation: watchFactoryToDiagnostic
324+
extraValidation: watchFactoryToDiagnostic,
325+
elementOptions: commandLineOptionsToMap([
326+
{
327+
name: "name",
328+
type: "string",
329+
description: Diagnostics.Specify_which_factory_to_invoke_watchFile_and_watchDirectory_on,
330+
},
331+
]),
322332
},
323333
];
324334

325-
function watchFactoryToDiagnostic(watchFactory: CompilerOptionsValue): [DiagnosticMessage] | undefined {
326-
return parsePackageName(watchFactory as string).rest ?
327-
[Diagnostics.watchFactory_name_can_only_be_a_package_name] :
328-
undefined;
335+
function watchFactoryToDiagnostic(watchFactory: CompilerOptionsValue, valueExpression: Expression | undefined) {
336+
const watchFactoryName = isString(watchFactory) ? watchFactory : (watchFactory as PluginImport).name;
337+
if (watchFactoryName && !parsePackageName(watchFactoryName).rest) return undefined;
338+
const diagnostics: DiagnosticAndArguments = [Diagnostics.watchFactory_name_can_only_be_a_package_name];
339+
if (!valueExpression || !isObjectLiteralExpression(valueExpression)) return diagnostics;
340+
const errorNode = forEachPropertyAssignment(valueExpression, "name", prop => prop.initializer);
341+
return errorNode ? { diagnostics, errorNode } : diagnostics;
329342
}
330343

331344
/** @internal */
@@ -1747,12 +1760,32 @@ export function parseListTypeOption(opt: CommandLineOptionOfListType, value = ""
17471760
return mapDefined(values, v => validateJsonOptionValue(opt.element, v || "", errors));
17481761
case "boolean":
17491762
case "object":
1763+
case "string | object":
17501764
return Debug.fail(`List of ${opt.element.type} is not yet supported.`);
17511765
default:
17521766
return mapDefined(values, v => parseCustomTypeOption(opt.element as CommandLineOptionOfCustomType, v, errors));
17531767
}
17541768
}
17551769

1770+
/** @internal */
1771+
export function parseObjectTypeOption(opt: CommandLineOptionOfObjectType, value: string | undefined, errors: Diagnostic[]): { value: CompilerOptionsValue | undefined } | undefined {
1772+
if (value === undefined) return undefined;
1773+
value = trimString(value);
1774+
if (startsWith(value, "-")) return undefined;
1775+
if (opt.type === "string | object" && !startsWith(value, "{")) {
1776+
return { value: validateJsonOptionValue(opt, value, errors) };
1777+
}
1778+
try {
1779+
const parsedValue = JSON.parse(value);
1780+
if (typeof parsedValue === "object") {
1781+
return { value: validateJsonOptionValue(opt, parsedValue, errors) };
1782+
}
1783+
}
1784+
catch { } // eslint-disable-line no-empty
1785+
errors.push(createCompilerDiagnostic(Diagnostics.Argument_for_0_option_must_be_Colon_1, opt.name, getCompilerOptionValueTypeString(opt)));
1786+
return { value: undefined };
1787+
}
1788+
17561789
/** @internal */
17571790
export interface OptionsBase {
17581791
[option: string]: CompilerOptionsValue | TsConfigSourceFile | undefined;
@@ -1930,9 +1963,15 @@ function parseOptionValue(
19301963
case "listOrElement":
19311964
Debug.fail("listOrElement not supported here");
19321965
break;
1966+
case "object":
1967+
case "string | object":
1968+
const objectResult = parseObjectTypeOption(opt, args[i], errors);
1969+
options[opt.name] = objectResult?.value;
1970+
if (objectResult) i++;
1971+
break;
19331972
// If not a primitive, the possible types are specified in what is effectively a map of options.
19341973
default:
1935-
options[opt.name] = parseCustomTypeOption(opt as CommandLineOptionOfCustomType, args[i], errors);
1974+
options[opt.name] = parseCustomTypeOption(opt, args[i], errors);
19361975
i++;
19371976
break;
19381977
}
@@ -2175,30 +2214,30 @@ const extendsOptionDeclaration: CommandLineOptionOfListType = {
21752214
type: "listOrElement",
21762215
element: {
21772216
name: "extends",
2178-
type: "string"
2217+
type: "string",
21792218
},
21802219
category: Diagnostics.File_Management,
21812220
disallowNullOrUndefined: true,
21822221
};
2183-
const compilerOptionsDeclaration: TsConfigOnlyOption = {
2222+
const compilerOptionsDeclaration: CommandLineOptionOfObjectType = {
21842223
name: "compilerOptions",
21852224
type: "object",
21862225
elementOptions: getCommandLineCompilerOptionsMap(),
21872226
extraKeyDiagnostics: compilerOptionsDidYouMeanDiagnostics,
21882227
};
2189-
const watchOptionsDeclaration: TsConfigOnlyOption = {
2228+
const watchOptionsDeclaration: CommandLineOptionOfObjectType = {
21902229
name: "watchOptions",
21912230
type: "object",
21922231
elementOptions: getCommandLineWatchOptionsMap(),
21932232
extraKeyDiagnostics: watchOptionsDidYouMeanDiagnostics,
21942233
};
2195-
const typeAcquisitionDeclaration: TsConfigOnlyOption = {
2234+
const typeAcquisitionDeclaration: CommandLineOptionOfObjectType = {
21962235
name: "typeAcquisition",
21972236
type: "object",
21982237
elementOptions: getCommandLineTypeAcquisitionMap(),
21992238
extraKeyDiagnostics: typeAcquisitionDidYouMeanDiagnostics
22002239
};
2201-
let _tsconfigRootOptions: TsConfigOnlyOption;
2240+
let _tsconfigRootOptions: CommandLineOptionOfObjectType;
22022241
function getTsconfigRootOptionsMap() {
22032242
if (_tsconfigRootOptions === undefined) {
22042243
_tsconfigRootOptions = {
@@ -2256,12 +2295,12 @@ function getTsconfigRootOptionsMap() {
22562295

22572296
/** @internal */
22582297
export interface JsonConversionNotifier {
2259-
rootOptions: TsConfigOnlyOption;
2298+
rootOptions: CommandLineOptionOfObjectType;
22602299
onPropertySet(
22612300
keyText: string,
22622301
value: any,
22632302
propertyAssignment: PropertyAssignment,
2264-
parentOption: TsConfigOnlyOption | undefined,
2303+
parentOption: CommandLineOptionOfObjectType | undefined,
22652304
option: CommandLineOption | undefined,
22662305
): void;
22672306
}
@@ -2322,7 +2361,7 @@ export function convertToJson(
23222361

23232362
function convertObjectLiteralExpressionToJson(
23242363
node: ObjectLiteralExpression,
2325-
objectOption: TsConfigOnlyOption | undefined,
2364+
objectOption: CommandLineOptionOfObjectType | undefined,
23262365
): any {
23272366
const result: any = returnValue ? {} : undefined;
23282367
for (const element of node.properties) {
@@ -2402,7 +2441,7 @@ export function convertToJson(
24022441
// that satifies it and need it to modify options set in them (for normalizing file paths)
24032442
// vs what we set in the json
24042443
// If need arises, we can modify this interface and callbacks as needed
2405-
return convertObjectLiteralExpressionToJson(objectLiteralExpression, option as TsConfigOnlyOption);
2444+
return convertObjectLiteralExpressionToJson(objectLiteralExpression, option as CommandLineOptionOfObjectType);
24062445

24072446
case SyntaxKind.ArrayLiteralExpression:
24082447
return convertArrayLiteralExpressionToJson(
@@ -2443,6 +2482,9 @@ function isCompilerOptionsValue(option: CommandLineOption | undefined, value: an
24432482
if (option.type === "listOrElement") {
24442483
return isArray(value) || isCompilerOptionsValue(option.element, value);
24452484
}
2485+
if (option.type === "string | object") {
2486+
return isString(value) || typeof value === "object";
2487+
}
24462488
const expectedType = isString(option.type) ? option.type : "string";
24472489
return typeof value === expectedType;
24482490
}
@@ -2552,6 +2594,7 @@ function getCustomTypeMapOfCommandLineOption(optionDefinition: CommandLineOption
25522594
case "number":
25532595
case "boolean":
25542596
case "object":
2597+
case "string | object":
25552598
// this is of a type CommandLineOptionOfPrimitiveType
25562599
return undefined;
25572600
case "list":
@@ -3268,7 +3311,7 @@ function parseOwnConfigOfJsonSourceFile(
32683311
keyText: string,
32693312
value: any,
32703313
propertyAssignment: PropertyAssignment,
3271-
parentOption: TsConfigOnlyOption | undefined,
3314+
parentOption: CommandLineOptionOfObjectType | undefined,
32723315
option: CommandLineOption | undefined,
32733316
) {
32743317
// Ensure value is verified except for extends which is handled in its own way for error reporting
@@ -3279,7 +3322,8 @@ function parseOwnConfigOfJsonSourceFile(
32793322
if (parentOption === compilerOptionsDeclaration) currentOption = options;
32803323
else if (parentOption === watchOptionsDeclaration) currentOption = watchOptions ??= {};
32813324
else if (parentOption === typeAcquisitionDeclaration) currentOption = typeAcquisition ??= getDefaultTypeAcquisition(configFileName);
3282-
else Debug.fail("Unknown option");
3325+
// Ignore anything other option that comes through as parent is not from root
3326+
else return;
32833327
currentOption[option.name] = value;
32843328
}
32853329
else if (keyText && parentOption?.extraKeyDiagnostics) {
@@ -3491,11 +3535,10 @@ export function convertJsonOption(
34913535
return undefined;
34923536
}
34933537
if (isCompilerOptionsValue(opt, value)) {
3494-
const optType = opt.type;
3495-
if ((optType === "list") && isArray(value)) {
3538+
if ((opt.type === "list") && isArray(value)) {
34963539
return convertJsonOptionOfListType(opt, value, basePath, errors, propertyAssignment, valueExpression as ArrayLiteralExpression | undefined, sourceFile);
34973540
}
3498-
else if (optType === "listOrElement") {
3541+
else if (opt.type === "listOrElement") {
34993542
return isArray(value) ?
35003543
convertJsonOptionOfListType(opt, value, basePath, errors, propertyAssignment, valueExpression as ArrayLiteralExpression | undefined, sourceFile) :
35013544
convertJsonOption(opt.element, value, basePath, errors, propertyAssignment, valueExpression, sourceFile);
@@ -3529,9 +3572,11 @@ function validateJsonOptionValue<T extends CompilerOptionsValue>(
35293572
sourceFile?: TsConfigSourceFile,
35303573
): T | undefined {
35313574
if (isNullOrUndefined(value)) return undefined;
3532-
const d = opt.extraValidation?.(value);
3575+
const d = opt.extraValidation?.(value, valueExpression);
35333576
if (!d) return value;
3534-
errors.push(createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, valueExpression, ...d));
3577+
const diagnostics = isArray(d) ? d : d.diagnostics;
3578+
const errorNode = isArray(d) ? valueExpression : d.errorNode;
3579+
errors.push(createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, errorNode, ...diagnostics));
35353580
return undefined;
35363581
}
35373582

@@ -3762,20 +3807,16 @@ function matchesExcludeWorker(
37623807
function validateSpecs(specs: readonly string[], errors: Diagnostic[], disallowTrailingRecursion: boolean, jsonSourceFile: TsConfigSourceFile | undefined, specKey: string): readonly string[] {
37633808
return specs.filter(spec => {
37643809
if (!isString(spec)) return false;
3765-
const diag = specToDiagnostic(spec, disallowTrailingRecursion);
3810+
const diag = specToDiagnostic(spec, /*valueExpresion*/ undefined, disallowTrailingRecursion);
37663811
if (diag !== undefined) {
3767-
errors.push(createDiagnostic(...diag));
3812+
const element = getTsConfigPropArrayElementValue(jsonSourceFile, specKey, spec);
3813+
errors.push(createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(jsonSourceFile, element, ...diag));
37683814
}
37693815
return diag === undefined;
37703816
});
3771-
3772-
function createDiagnostic(message: DiagnosticMessage, spec: string): Diagnostic {
3773-
const element = getTsConfigPropArrayElementValue(jsonSourceFile, specKey, spec);
3774-
return createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(jsonSourceFile, element, message, spec);
3775-
}
37763817
}
37773818

3778-
function specToDiagnostic(spec: CompilerOptionsValue, disallowTrailingRecursion?: boolean): [DiagnosticMessage, string] | undefined {
3819+
function specToDiagnostic(spec: CompilerOptionsValue, _valueExpresion?: Expression, disallowTrailingRecursion?: boolean): DiagnosticAndArguments | undefined {
37793820
Debug.assert(typeof spec === "string");
37803821
if (disallowTrailingRecursion && invalidTrailingRecursionPattern.test(spec)) {
37813822
return [Diagnostics.File_specification_cannot_end_in_a_recursive_directory_wildcard_Asterisk_Asterisk_Colon_0, spec];
@@ -3940,6 +3981,7 @@ function getOptionValueWithEmptyStrings(value: any, option: CommandLineOption):
39403981
if (value === undefined) return value;
39413982
switch (option.type) {
39423983
case "object": // "paths". Can't get any useful information from the value since we blank out strings, so just return "".
3984+
case "string | object":
39433985
return "";
39443986
case "string": // Could be any arbitrary string -- use empty string instead.
39453987
return "";
@@ -3970,6 +4012,7 @@ function getDefaultValueForOption(option: CommandLineOption): {} {
39704012
case "boolean":
39714013
return true;
39724014
case "string":
4015+
case "string | object":
39734016
const defaultValue = option.defaultValueDescription;
39744017
return option.isFilePath ? `./${defaultValue && typeof defaultValue === "string" ? defaultValue : ""}` : "";
39754018
case "list":

src/compiler/types.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6952,7 +6952,7 @@ export interface Diagnostic extends DiagnosticRelatedInformation {
69526952
export type DiagnosticArguments = (string | number)[];
69536953

69546954
/** @internal */
6955-
export type DiagnosticAndArguments = [message: DiagnosticMessage, ...args: DiagnosticArguments];
6955+
export type DiagnosticAndArguments = readonly [message: DiagnosticMessage, ...args: DiagnosticArguments];
69566956

69576957
export interface DiagnosticRelatedInformation {
69586958
category: DiagnosticCategory;
@@ -7369,10 +7369,16 @@ export interface CreateProgramOptions {
73697369
typeScriptVersion?: string;
73707370
}
73717371

7372+
/** @internal */
7373+
export type CommandLineOptionExtraValidation = (value: CompilerOptionsValue, valueExpression?: Expression) =>
7374+
DiagnosticAndArguments |
7375+
{ diagnostics: DiagnosticAndArguments; errorNode: Expression; } |
7376+
undefined;
7377+
73727378
/** @internal */
73737379
export interface CommandLineOptionBase {
73747380
name: string;
7375-
type: "string" | "number" | "boolean" | "object" | "list" | "listOrElement" | Map<string, number | string>; // a value of a primitive type, or an object literal mapping named values to actual values
7381+
type: "string" | "number" | "boolean" | "object" | "list" | "listOrElement" | "string | object" | Map<string, number | string>; // a value of a primitive type, or an object literal mapping named values to actual values
73767382
isFilePath?: boolean; // True if option value is a path or fileName
73777383
shortName?: string; // A short mnemonic for convenience - for instance, 'h' can be used in place of 'help'
73787384
description?: DiagnosticMessage; // The message describing what the command line switch does.
@@ -7392,8 +7398,8 @@ export interface CommandLineOptionBase {
73927398
affectsDeclarationPath?: true; // true if the options affects declaration file path computed
73937399
affectsBuildInfo?: true; // true if this options should be emitted in buildInfo
73947400
transpileOptionValue?: boolean | undefined; // If set this means that the option should be set to this value when transpiling
7395-
extraValidation?: (value: CompilerOptionsValue) => [DiagnosticMessage, ...string[]] | undefined; // Additional validation to be performed for the value to be valid
7396-
disallowNullOrUndefined?: true; // If set option does not allow setting null
7401+
extraValidation?: CommandLineOptionExtraValidation; // Additional validation to be performed for the value to be valid
7402+
disallowNullOrUndefined?: true; // If set option does not allow setting null
73977403
}
73987404

73997405
/** @internal */
@@ -7436,21 +7442,21 @@ export interface DidYouMeanOptionsDiagnostics {
74367442
}
74377443

74387444
/** @internal */
7439-
export interface TsConfigOnlyOption extends CommandLineOptionBase {
7440-
type: "object";
7445+
export interface CommandLineOptionOfObjectType extends CommandLineOptionBase {
7446+
type: "object" | "string | object";
74417447
elementOptions?: Map<string, CommandLineOption>;
74427448
extraKeyDiagnostics?: DidYouMeanOptionsDiagnostics;
74437449
}
74447450

74457451
/** @internal */
74467452
export interface CommandLineOptionOfListType extends CommandLineOptionBase {
74477453
type: "list" | "listOrElement";
7448-
element: CommandLineOptionOfCustomType | CommandLineOptionOfStringType | CommandLineOptionOfNumberType | CommandLineOptionOfBooleanType | TsConfigOnlyOption;
7454+
element: CommandLineOptionOfCustomType | CommandLineOptionOfStringType | CommandLineOptionOfNumberType | CommandLineOptionOfBooleanType | CommandLineOptionOfObjectType;
74497455
listPreserveFalsyValues?: boolean;
74507456
}
74517457

74527458
/** @internal */
7453-
export type CommandLineOption = CommandLineOptionOfCustomType | CommandLineOptionOfStringType | CommandLineOptionOfNumberType | CommandLineOptionOfBooleanType | TsConfigOnlyOption | CommandLineOptionOfListType;
7459+
export type CommandLineOption = CommandLineOptionOfCustomType | CommandLineOptionOfStringType | CommandLineOptionOfNumberType | CommandLineOptionOfBooleanType | CommandLineOptionOfObjectType | CommandLineOptionOfListType;
74547460

74557461
/** @internal */
74567462
export const enum CharacterCodes {

src/executeCommandLine/executeCommandLine.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ function generateOptionOutput(sys: System, option: CommandLineOption, rightAlign
370370
case "string":
371371
case "number":
372372
case "boolean":
373+
case "string | object":
373374
return getDiagnosticText(Diagnostics.type_Colon);
374375
case "list":
375376
return getDiagnosticText(Diagnostics.one_or_more_Colon);
@@ -390,6 +391,9 @@ function generateOptionOutput(sys: System, option: CommandLineOption, rightAlign
390391
case "listOrElement":
391392
possibleValues = getPossibleValues(option.element);
392393
break;
394+
case "string | object":
395+
possibleValues = "string";
396+
break;
393397
case "object":
394398
possibleValues = "";
395399
break;

src/harness/harnessIO.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,8 +385,11 @@ export namespace Compiler {
385385
case "list":
386386
case "listOrElement":
387387
return ts.parseListTypeOption(option, value, errors);
388+
case "object":
389+
case "string | object":
390+
return ts.parseObjectTypeOption(option, value, errors)?.value;
388391
default:
389-
return ts.parseCustomTypeOption(option as ts.CommandLineOptionOfCustomType, value, errors);
392+
return ts.parseCustomTypeOption(option, value, errors);
390393
}
391394
}
392395

0 commit comments

Comments
 (0)