From 858e1001360b89474fbe9968516819be770acc29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Viken=20Valv=C3=A5g?= Date: Mon, 26 Nov 2018 21:34:25 +0100 Subject: [PATCH 01/17] Updated typings for yargs, inferring the shape of the parsed arguments. --- types/yargs/index.d.ts | 308 ++++++++++++++++++++----------------- types/yargs/tsconfig.json | 2 +- types/yargs/yargs-tests.ts | 41 ++++- 3 files changed, 212 insertions(+), 139 deletions(-) diff --git a/types/yargs/index.d.ts b/types/yargs/index.d.ts index c32bcc0b11..1a025b738f 100644 --- a/types/yargs/index.d.ts +++ b/types/yargs/index.d.ts @@ -6,6 +6,7 @@ // Jeff Kenney // Jimi (Dimitris) Charalampidis // Teddy Cross +// Steffen Viken Valvåg // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped // TypeScript Version: 2.2 @@ -14,220 +15,234 @@ // Convention: // Use 'union types' when: -// - parameter types have similar signature type (i.e. 'string | string[]') +// - parameter types have similar signature type (i.e. 'string | ReadonlyArray') // - parameter names have the same semantic meaning (i.e. ['command', 'commands'] , ['key', 'keys']) // An example for not using 'union types' is the declaration of 'env' where `prefix` and `enable` parameters // have different semantics. On the other hand, in the declaration of 'usage', a `command: string` parameter -// has the same semantic meaning with declaring an overload method by using `commands: string[]`, thus -// it's prefered to use `command: string | string[]` +// has the same semantic meaning with declaring an overload method by using `commands: ReadonlyArray`, +// thus it's preferred to use `command: string | ReadonlyArray` // Use parameterless declaration instead of declaring all parameters optional, // when all parameters are optional and more than one declare namespace yargs { - interface Argv { - (): Arguments; - (args: string[], cwd?: string): Arguments; + // The type parameter T is the expected shape of the parsed arguments. + // Arguments is those arguments plus _ and $0, and an indexer falling + // back to any for unknown arguments. + // + // For the return type / argv property, we create a mapped type over + // Arguments to simplify the inferred type signature in client code. + interface Argv { + (): { [K in keyof Arguments]: Arguments[K] }; + (args: ReadonlyArray, cwd?: string): { [K in keyof Arguments]: Arguments[K] }; - alias(shortName: string | string[], longName: string | string[]): Argv; - alias(aliases: { [shortName: string]: string | string[] }): Argv; + /* Aliases for previously declared arguments can inherit the types of those arguments. */ + alias(shortName: K1, longName: K2): Argv; + alias(shortName: K2, longName: K1): Argv; + + alias(shortName: string | ReadonlyArray, longName: string | ReadonlyArray): Argv; + alias(aliases: { [shortName: string]: string | ReadonlyArray }): Argv; - argv: Arguments; + argv: { [K in keyof Arguments]: Arguments[K] }; - array(key: string | string[]): Argv; + array(key: K | ReadonlyArray): Argv; - boolean(key: string | string[]): Argv; + boolean(key: K | ReadonlyArray): Argv; - check(func: (argv: Arguments, aliases: { [alias: string]: string }) => any, global?: boolean): Argv; + check(func: (argv: Arguments, aliases: { [alias: string]: string }) => any, global?: boolean): Argv; - choices(key: string, values: Choices): Argv; - choices(choices: { [argName: string]: Choices }): Argv; + choices>(key: K, values: C): Argv; + choices }>(choices: C): Argv; - coerce(key: string | string[], func: (arg: any) => any): Argv; - coerce(opts: { [key: string]: (arg: any) => any; }): Argv; + /* For previously declared arguments, we can infer the parameter type of the coercion function. */ + coerce(key: K | ReadonlyArray, func: (arg: T[K]) => V): Argv; + coerce any }>(opts: O): Argv }>; - command(command: string | string[], description: string, builder?: (args: Argv) => Argv, handler?: (args: Arguments) => void): Argv; - command(command: string | string[], description: string, builder?: { [key: string]: Options }, handler?: (args: Arguments) => void): Argv; - command(command: string | string[], description: string, module: CommandModule): Argv; - command(command: string | string[], showInHelp: false, builder?: (args: Argv) => Argv, handler?: (args: Arguments) => void): Argv; - command(command: string | string[], showInHelp: false, builder?: { [key: string]: Options }, handler?: (args: Arguments) => void): Argv; - command(command: string | string[], showInHelp: false, module: CommandModule): Argv; - command(module: CommandModule): Argv; + coerce(key: K | ReadonlyArray, func: (arg: any) => V): Argv; + coerce any }>(opts: O): Argv }>; + + command(command: string | ReadonlyArray, description: string, builder?: (args: Argv) => Argv, handler?: (args: Arguments) => void): Argv; + command(command: string | ReadonlyArray, description: string, builder?: O, handler?: (args: Arguments>) => void): Argv; + command(command: string | ReadonlyArray, description: string, module: CommandModule): Argv; + command(command: string | ReadonlyArray, showInHelp: false, builder?: (args: Argv) => Argv, handler?: (args: Arguments) => void): Argv; + command(command: string | ReadonlyArray, showInHelp: false, builder?: O, handler?: (args: Arguments>) => void): Argv; + command(command: string | ReadonlyArray, showInHelp: false, module: CommandModule): Argv; + command(module: CommandModule): Argv; // Advanced API - commandDir(dir: string, opts?: RequireDirectoryOptions): Argv; + commandDir(dir: string, opts?: RequireDirectoryOptions): Argv; - completion(): Argv; - completion(cmd: string, func?: AsyncCompletionFunction): Argv; - completion(cmd: string, func?: SyncCompletionFunction): Argv; - completion(cmd: string, description?: string, func?: AsyncCompletionFunction): Argv; - completion(cmd: string, description?: string, func?: SyncCompletionFunction): Argv; + completion(): Argv; + completion(cmd: string, func?: AsyncCompletionFunction): Argv; + completion(cmd: string, func?: SyncCompletionFunction): Argv; + completion(cmd: string, description?: string, func?: AsyncCompletionFunction): Argv; + completion(cmd: string, description?: string, func?: SyncCompletionFunction): Argv; - config(): Argv; - config(key: string | string[], description?: string, parseFn?: (configPath: string) => object): Argv; - config(key: string | string[], parseFn: (configPath: string) => object): Argv; - config(explicitConfigurationObject: object): Argv; + config(): Argv; + config(key: string | ReadonlyArray, description?: string, parseFn?: (configPath: string) => object): Argv; + config(key: string | ReadonlyArray, parseFn: (configPath: string) => object): Argv; + config(explicitConfigurationObject: object): Argv; - conflicts(key: string, value: string | string[]): Argv; - conflicts(conflicts: { [key: string]: string | string[] }): Argv; + conflicts(key: string, value: string | ReadonlyArray): Argv; + conflicts(conflicts: { [key: string]: string | ReadonlyArray }): Argv; - count(key: string | string[]): Argv; + count(key: K | ReadonlyArray): Argv; - default(key: string, value: any, description?: string): Argv; - default(defaults: { [key: string]: any }, description?: string): Argv; + default(key: K, value: V, description?: string): Argv; + default(defaults: D, description?: string): Argv; /** * @deprecated since version 6.6.0 * Use '.demandCommand()' or '.demandOption()' instead */ - demand(key: string | string[], msg: string): Argv; - demand(key: string | string[], required?: boolean): Argv; - demand(positionals: number, msg: string): Argv; - demand(positionals: number, required?: boolean): Argv; - demand(positionals: number, max: number, msg?: string): Argv; + demand(key: string | ReadonlyArray, msg: string): Argv; + demand(key: string | ReadonlyArray, required?: boolean): Argv; + demand(positionals: number, msg: string): Argv; + demand(positionals: number, required?: boolean): Argv; + demand(positionals: number, max: number, msg?: string): Argv; - demandOption(key: string | string[], msg?: string): Argv; - demandOption(key: string | string[], demand?: boolean): Argv; + demandOption(key: string | ReadonlyArray, msg?: string): Argv; + demandOption(key: string | ReadonlyArray, demand?: boolean): Argv; - demandCommand(): Argv; - demandCommand(min: number, minMsg?: string): Argv; - demandCommand(min: number, max?: number, minMsg?: string, maxMsg?: string): Argv; + demandCommand(): Argv; + demandCommand(min: number, minMsg?: string): Argv; + demandCommand(min: number, max?: number, minMsg?: string, maxMsg?: string): Argv; - describe(key: string | string[], description: string): Argv; - describe(descriptions: { [key: string]: string }): Argv; + describe(key: string | ReadonlyArray, description: string): Argv; + describe(descriptions: { [key: string]: string }): Argv; - detectLocale(detect: boolean): Argv; + detectLocale(detect: boolean): Argv; - env(): Argv; - env(prefix: string): Argv; - env(enable: boolean): Argv; + env(): Argv; + env(prefix: string): Argv; + env(enable: boolean): Argv; - epilog(msg: string): Argv; + epilog(msg: string): Argv; - epilogue(msg: string): Argv; + epilogue(msg: string): Argv; - example(command: string, description: string): Argv; + example(command: string, description: string): Argv; - exitProcess(enabled: boolean): Argv; + exitProcess(enabled: boolean): Argv; - fail(func: (msg: string, err: Error) => any): Argv; + fail(func: (msg: string, err: Error) => any): Argv; - getCompletion(args: string[], done: (completions: string[]) => void): Argv; + getCompletion(args: ReadonlyArray, done: (completions: ReadonlyArray) => void): Argv; - global(key: string | string[]): Argv; + global(key: string | ReadonlyArray): Argv; - group(key: string | string[], groupName: string): Argv; + group(key: string | ReadonlyArray, groupName: string): Argv; - hide(key: string): Argv; + hide(key: string): Argv; - help(): Argv; - help(enableExplicit: boolean): Argv; - help(option: string, enableExplicit: boolean): Argv; - help(option: string, description?: string, enableExplicit?: boolean): Argv; + help(): Argv; + help(enableExplicit: boolean): Argv; + help(option: string, enableExplicit: boolean): Argv; + help(option: string, description?: string, enableExplicit?: boolean): Argv; - implies(key: string, value: string | string[]): Argv; - implies(implies: { [key: string]: string | string[] }): Argv; + implies(key: string, value: string | ReadonlyArray): Argv; + implies(implies: { [key: string]: string | ReadonlyArray }): Argv; locale(): string; - locale(loc: string): Argv; + locale(loc: string): Argv; - middleware(callbacks: MiddlewareFunction | MiddlewareFunction[]): Argv; + middleware(callbacks: MiddlewareFunction | ReadonlyArray>): Argv; - nargs(key: string, count: number): Argv; - nargs(nargs: { [key: string]: number }): Argv; + nargs(key: string, count: number): Argv; + nargs(nargs: { [key: string]: number }): Argv; - normalize(key: string | string[]): Argv; + normalize(key: K | ReadonlyArray): Argv; - number(key: string | string[]): Argv; + number(key: K | ReadonlyArray): Argv; - option(key: string, options: Options): Argv; - option(options: { [key: string]: Options }): Argv; + option(key: K, options: O): Argv }>; + option(options: O): Argv>; - options(key: string, options: Options): Argv; - options(options: { [key: string]: Options }): Argv; + options(key: K, options: O): Argv }>; + options(options: O): Argv>; - parse(): Arguments; - parse(arg: string | string[], context?: object, parseCallback?: ParseCallback): Arguments; + parse(): Arguments; + parse(arg: string | ReadonlyArray, context?: object, parseCallback?: ParseCallback): Arguments; - pkgConf(key: string | string[], cwd?: string): Argv; + pkgConf(key: string | ReadonlyArray, cwd?: string): Argv; /** * 'positional' should be called in a command's builder function, and is not * available on the top-level yargs instance. If so, it will throw an error. */ - positional(key: string, opt: PositionalOptions): Argv; + positional(key: string, opt: PositionalOptions): Argv; - recommendCommands(): Argv; + recommendCommands(): Argv; /** * @deprecated since version 6.6.0 * Use '.demandCommand()' or '.demandOption()' instead */ - require(key: string, msg: string): Argv; - require(key: string, required: boolean): Argv; - require(keys: number[], msg: string): Argv; - require(keys: number[], required: boolean): Argv; - require(positionals: number, required: boolean): Argv; - require(positionals: number, msg: string): Argv; + require(key: string, msg: string): Argv; + require(key: string, required: boolean): Argv; + require(keys: ReadonlyArray, msg: string): Argv; + require(keys: ReadonlyArray, required: boolean): Argv; + require(positionals: number, required: boolean): Argv; + require(positionals: number, msg: string): Argv; /** * @deprecated since version 6.6.0 * Use '.demandCommand()' or '.demandOption()' instead */ - required(key: string, msg: string): Argv; - required(key: string, required: boolean): Argv; - required(keys: number[], msg: string): Argv; - required(keys: number[], required: boolean): Argv; - required(positionals: number, required: boolean): Argv; - required(positionals: number, msg: string): Argv; + required(key: string, msg: string): Argv; + required(key: string, required: boolean): Argv; + required(keys: ReadonlyArray, msg: string): Argv; + required(keys: ReadonlyArray, required: boolean): Argv; + required(positionals: number, required: boolean): Argv; + required(positionals: number, msg: string): Argv; - requiresArg(key: string | string[]): Argv; + requiresArg(key: string | ReadonlyArray): Argv; /** * @deprecated since version 6.6.0 * Use '.global()' instead */ - reset(): Argv; + reset(): Argv; - scriptName($0: string): Argv; + scriptName($0: string): Argv; - showCompletionScript(): Argv; + showCompletionScript(): Argv; - showHidden(option?: string | boolean): Argv; - showHidden(option: string, description?: string): Argv; + showHidden(option?: string | boolean): Argv; + showHidden(option: string, description?: string): Argv; - showHelp(consoleLevel?: string): Argv; + showHelp(consoleLevel?: string): Argv; - showHelpOnFail(enable: boolean, message?: string): Argv; + showHelpOnFail(enable: boolean, message?: string): Argv; - skipValidation(key: string | string[]): Argv; + skipValidation(key: string | ReadonlyArray): Argv; - strict(): Argv; + strict(): Argv; - string(key: string | string[]): Argv; + string(key: K | ReadonlyArray): Argv; // Intended to be used with '.wrap()' terminalWidth(): number; - updateLocale(obj: { [key: string]: string }): Argv; + updateLocale(obj: { [key: string]: string }): Argv; - updateStrings(obj: { [key: string]: string }): Argv; + updateStrings(obj: { [key: string]: string }): Argv; - usage(message: string): Argv; - usage(command: string | string[], description: string, builder?: (args: Argv) => Argv, handler?: (args: Arguments) => void): Argv; - usage(command: string | string[], showInHelp: boolean, builder?: (args: Argv) => Argv, handler?: (args: Arguments) => void): Argv; - usage(command: string | string[], description: string, builder?: { [key: string]: Options }, handler?: (args: Arguments) => void): Argv; - usage(commands: string | string[], showInHelp: boolean, builder?: { [key: string]: Options }, handler?: (args: Arguments) => void): Argv; + usage(message: string): Argv; + usage(command: string | ReadonlyArray, description: string, builder?: (args: Argv) => Argv, handler?: (args: Arguments) => void): Argv; + usage(command: string | ReadonlyArray, showInHelp: boolean, builder?: (args: Argv) => Argv, handler?: (args: Arguments) => void): Argv; + usage(command: string | ReadonlyArray, description: string, builder?: O, handler?: (args: Arguments>) => void): Argv; + usage(command: string | ReadonlyArray, showInHelp: boolean, builder?: O, handler?: (args: Arguments>) => void): Argv; - version(): Argv; - version(version: string): Argv; - version(enable: boolean): Argv; - version(optionKey: string, version: string): Argv; - version(optionKey: string, description: string, version: string): Argv; + version(): Argv; + version(version: string): Argv; + version(enable: boolean): Argv; + version(optionKey: string, version: string): Argv; + version(optionKey: string, description: string, version: string): Argv; - wrap(columns: number | null): Argv; + wrap(columns: number | null): Argv; } - interface Arguments { + type Arguments = T & { /** Non-option arguments */ _: string[]; /** The script name or node command */ @@ -238,21 +253,21 @@ declare namespace yargs { interface RequireDirectoryOptions { recurse?: boolean; - extensions?: string[]; + extensions?: ReadonlyArray; visit?: (commandObject: any, pathToFile?: string, filename?: string) => any; include?: RegExp | ((pathToFile: string) => boolean); exclude?: RegExp | ((pathToFile: string) => boolean); } interface Options { - alias?: string | string[]; + alias?: string | ReadonlyArray; array?: boolean; boolean?: boolean; choices?: Choices; coerce?: (arg: any) => any; config?: boolean; configParser?: (configPath: string) => object; - conflicts?: string | string[] | { [key: string]: string | string[] }; + conflicts?: string | ReadonlyArray | { [key: string]: string | ReadonlyArray }; count?: boolean; default?: any; defaultDescription?: string; @@ -268,7 +283,7 @@ declare namespace yargs { global?: boolean; group?: string; hidden?: boolean; - implies?: string | string[] | { [key: string]: string | string[] }; + implies?: string | ReadonlyArray | { [key: string]: string | ReadonlyArray }; nargs?: number; normalize?: boolean; number?: boolean; @@ -289,32 +304,51 @@ declare namespace yargs { } interface PositionalOptions { - alias?: string | string[]; + alias?: string | ReadonlyArray; choices?: Choices; coerce?: (arg: any) => any; - conflicts?: string | string[] | { [key: string]: string | string[] }; + conflicts?: string | ReadonlyArray | { [key: string]: string | ReadonlyArray }; default?: any; desc?: string; describe?: string; description?: string; - implies?: string | string[] | { [key: string]: string | string[] }; + implies?: string | ReadonlyArray | { [key: string]: string | ReadonlyArray }; normalize?: boolean; type?: PositionalOptionsType; } - interface CommandModule { - aliases?: string[] | string; - builder?: CommandBuilder; - command?: string[] | string; + type InferredOptionType = + O extends { type: "array" } ? string[] : + O extends { type: "boolean" } ? boolean : + O extends { type: "number" } ? number : + O extends { type: "string" } ? string : + O extends { type: "count" } ? number : + O extends { array: true } ? string[] : + O extends { boolean: true } ? boolean : + O extends { number: true } ? number : + O extends { string: true } ? string : + O extends { normalize: true } ? string : + O extends { choices: Array } ? C : + O extends { default: infer D } ? D : + O extends { coerce: (arg: any) => infer T } ? T : + any; + + type InferredOptionTypes = + { [K in keyof O]: InferredOptionType }; + + interface CommandModule { + aliases?: ReadonlyArray | string; + builder?: CommandBuilder; + command?: ReadonlyArray | string; describe?: string | false; - handler: (args: any) => void; + handler: (args: Argv) => void; } - type ParseCallback = (err: Error | undefined, argv: Arguments, output: string) => void; - type CommandBuilder = { [key: string]: Options } | ((args: Argv) => Argv); + type ParseCallback = (err: Error | undefined, argv: Arguments, output: string) => void; + type CommandBuilder = { [key: string]: Options } | ((args: Argv) => Argv); type SyncCompletionFunction = (current: string, argv: any) => string[]; - type AsyncCompletionFunction = (current: string, argv: any, done: (completion: string[]) => void) => void; - type MiddlewareFunction = (args: Arguments) => void; + type AsyncCompletionFunction = (current: string, argv: any, done: (completion: ReadonlyArray) => void) => void; + type MiddlewareFunction = (args: Arguments) => void; type Choices = Array; type PositionalOptionsType = "boolean" | "number" | "string"; } diff --git a/types/yargs/tsconfig.json b/types/yargs/tsconfig.json index 003b792854..dd10c56ee2 100644 --- a/types/yargs/tsconfig.json +++ b/types/yargs/tsconfig.json @@ -6,7 +6,7 @@ ], "noImplicitAny": true, "noImplicitThis": true, - "strictNullChecks": false, + "strictNullChecks": true, "strictFunctionTypes": true, "baseUrl": "../", "typeRoots": [ diff --git a/types/yargs/yargs-tests.ts b/types/yargs/yargs-tests.ts index d8b301dc1c..8452b4ec34 100644 --- a/types/yargs/yargs-tests.ts +++ b/types/yargs/yargs-tests.ts @@ -424,7 +424,7 @@ function Argv$commandDirWithOptions() { .commandDir('.', { recurse: false, extensions: ['js'], - visit: (commandObject: any, pathToFile: string, filename: string) => void 0, + visit: (commandObject: any, pathToFile?: string, filename?: string) => void 0, include: /.*\.js$/, exclude: /.*\.spec.js$/, }) @@ -666,3 +666,42 @@ function Argv$scriptName() { const ya = yargs .scriptName("my-script"); } + +function Argv$inferOptionTypes() { + const argv1 = yargs + .option("u", { type: "string" }) + .alias("u", "url") + .argv; + + argv1.u; // string + argv1.url; // string + + const argv2 = yargs + .option("verbose", { default: false }) + .alias("v", "verbose") + .argv; + + argv2.v; // boolean + argv2.verbose; // boolean + + const argv3 = yargs + .option({ + count: { number: true }, + date: { coerce: Date.parse } + }) + .alias("c", "count") + .argv; + + argv3.c; // number + argv3.count; // number + argv3.date; // number + + const argv4 = yargs + .string(["u", "url"]) + .alias("url", "uri") + .argv; + + argv4.u; // string + argv4.url; // string + argv4.uri; // string +} \ No newline at end of file From 2a1f1b31261f4bf65e8560a9f1aa8aab599f393f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Viken=20Valv=C3=A5g?= Date: Mon, 26 Nov 2018 23:09:18 +0100 Subject: [PATCH 02/17] Fix lint errors. --- types/yargs/index.d.ts | 11 +++++------ types/yargs/yargs-tests.ts | 6 +++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/types/yargs/index.d.ts b/types/yargs/index.d.ts index 1a025b738f..fce6d279a2 100644 --- a/types/yargs/index.d.ts +++ b/types/yargs/index.d.ts @@ -8,7 +8,7 @@ // Teddy Cross // Steffen Viken Valvåg // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// TypeScript Version: 2.2 +// TypeScript Version: 2.8 // The following TSLint rules have been disabled: // unified-signatures: Because there is useful information in the argument names of the overloaded signatures @@ -38,7 +38,7 @@ declare namespace yargs { /* Aliases for previously declared arguments can inherit the types of those arguments. */ alias(shortName: K1, longName: K2): Argv; alias(shortName: K2, longName: K1): Argv; - + alias(shortName: string | ReadonlyArray, longName: string | ReadonlyArray): Argv; alias(aliases: { [shortName: string]: string | ReadonlyArray }): Argv; @@ -62,7 +62,7 @@ declare namespace yargs { command(command: string | ReadonlyArray, description: string, builder?: (args: Argv) => Argv, handler?: (args: Arguments) => void): Argv; command(command: string | ReadonlyArray, description: string, builder?: O, handler?: (args: Arguments>) => void): Argv; - command(command: string | ReadonlyArray, description: string, module: CommandModule): Argv; + command(command: string | ReadonlyArray, description: string, module: CommandModule): Argv; command(command: string | ReadonlyArray, showInHelp: false, builder?: (args: Argv) => Argv, handler?: (args: Arguments) => void): Argv; command(command: string | ReadonlyArray, showInHelp: false, builder?: O, handler?: (args: Arguments>) => void): Argv; command(command: string | ReadonlyArray, showInHelp: false, module: CommandModule): Argv; @@ -249,7 +249,7 @@ declare namespace yargs { $0: string; /** All remaining options */ [argName: string]: any; - } + }; interface RequireDirectoryOptions { recurse?: boolean; @@ -333,8 +333,7 @@ declare namespace yargs { O extends { coerce: (arg: any) => infer T } ? T : any; - type InferredOptionTypes = - { [K in keyof O]: InferredOptionType }; + type InferredOptionTypes = { [K in keyof O]: InferredOptionType }; interface CommandModule { aliases?: ReadonlyArray | string; diff --git a/types/yargs/yargs-tests.ts b/types/yargs/yargs-tests.ts index 8452b4ec34..f2b2ad2ada 100644 --- a/types/yargs/yargs-tests.ts +++ b/types/yargs/yargs-tests.ts @@ -672,7 +672,7 @@ function Argv$inferOptionTypes() { .option("u", { type: "string" }) .alias("u", "url") .argv; - + argv1.u; // string argv1.url; // string @@ -691,7 +691,7 @@ function Argv$inferOptionTypes() { }) .alias("c", "count") .argv; - + argv3.c; // number argv3.count; // number argv3.date; // number @@ -704,4 +704,4 @@ function Argv$inferOptionTypes() { argv4.u; // string argv4.url; // string argv4.uri; // string -} \ No newline at end of file +} From a17f0a03801b459f54be89dcc57828be2a8662fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Viken=20Valv=C3=A5g?= Date: Mon, 26 Nov 2018 23:22:14 +0100 Subject: [PATCH 03/17] Use $ExpectType, and add more tests. --- types/yargs/yargs-tests.ts | 63 ++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/types/yargs/yargs-tests.ts b/types/yargs/yargs-tests.ts index f2b2ad2ada..f32023ec99 100644 --- a/types/yargs/yargs-tests.ts +++ b/types/yargs/yargs-tests.ts @@ -673,16 +673,20 @@ function Argv$inferOptionTypes() { .alias("u", "url") .argv; - argv1.u; // string - argv1.url; // string + // $ExpectType string + argv1.u; + // $ExpectType string + argv1.url; const argv2 = yargs .option("verbose", { default: false }) .alias("v", "verbose") .argv; - argv2.v; // boolean - argv2.verbose; // boolean + // $ExpectType boolean + argv2.v; + // $ExpectType boolean + argv2.verbose; const argv3 = yargs .option({ @@ -692,16 +696,55 @@ function Argv$inferOptionTypes() { .alias("c", "count") .argv; - argv3.c; // number - argv3.count; // number - argv3.date; // number + // $ExpectType number + argv3.c; + // $ExpectType number + argv3.count; + // $ExpectType number + argv3.date; const argv4 = yargs .string(["u", "url"]) .alias("url", "uri") .argv; - argv4.u; // string - argv4.url; // string - argv4.uri; // string + // $ExpectType string + argv4.u; + // $ExpectType string + argv4.url; + // $ExpectType string + argv4.uri; + // $ExpectType any + argv4.bogus; + + type Color = "red" | "blue" | "green"; + const colors: Color[] = ["red", "blue", "green"]; + + const argv5 = yargs + .array("array") + .boolean("boolean") + .choices("color", colors) + .count("count") + .default("date", new Date()) + .normalize("normalize") + .number("number") + .string("string") + .argv; + + // $ExpectType string[] + argv5.array; + // $ExpectType boolean + argv5.boolean; + // $ExpectType Color + argv5.color; + // $ExpectType number + argv5.count; + // $ExpectType Date + argv5.date; + // $ExpectType string + argv5.normalize; + // $ExpectType number + argv5.number; + // $ExpectType string + argv5.string; } From c93b56a72c3506c019dcffce25d7f4cb21214b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Viken=20Valv=C3=A5g?= Date: Mon, 26 Nov 2018 23:49:04 +0100 Subject: [PATCH 04/17] Updated comments. --- types/yargs/index.d.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/types/yargs/index.d.ts b/types/yargs/index.d.ts index fce6d279a2..1c40eea4c5 100644 --- a/types/yargs/index.d.ts +++ b/types/yargs/index.d.ts @@ -25,9 +25,9 @@ // when all parameters are optional and more than one declare namespace yargs { - // The type parameter T is the expected shape of the parsed arguments. - // Arguments is those arguments plus _ and $0, and an indexer falling - // back to any for unknown arguments. + // The type parameter T is the expected shape of the parsed options. + // Arguments is those options plus _ and $0, and an indexer falling + // back to any for unknown options. // // For the return type / argv property, we create a mapped type over // Arguments to simplify the inferred type signature in client code. @@ -35,7 +35,7 @@ declare namespace yargs { (): { [K in keyof Arguments]: Arguments[K] }; (args: ReadonlyArray, cwd?: string): { [K in keyof Arguments]: Arguments[K] }; - /* Aliases for previously declared arguments can inherit the types of those arguments. */ + // Aliases for previously declared options can inherit the types of those options. alias(shortName: K1, longName: K2): Argv; alias(shortName: K2, longName: K1): Argv; @@ -53,7 +53,7 @@ declare namespace yargs { choices>(key: K, values: C): Argv; choices }>(choices: C): Argv; - /* For previously declared arguments, we can infer the parameter type of the coercion function. */ + // For previously declared options, we can infer the parameter type of the coercion function. coerce(key: K | ReadonlyArray, func: (arg: T[K]) => V): Argv; coerce any }>(opts: O): Argv }>; From a5f3d28c1586222844571d5ac788bddd1c33c1e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Viken=20Valv=C3=A5g?= Date: Sat, 1 Dec 2018 16:53:47 +0100 Subject: [PATCH 05/17] Slight improvement to alias, organize tests more systematically. --- types/yargs/index.d.ts | 4 +- types/yargs/yargs-tests.ts | 121 ++++++++++++++++++++----------------- 2 files changed, 67 insertions(+), 58 deletions(-) diff --git a/types/yargs/index.d.ts b/types/yargs/index.d.ts index 1c40eea4c5..a4e9b6bce5 100644 --- a/types/yargs/index.d.ts +++ b/types/yargs/index.d.ts @@ -36,8 +36,8 @@ declare namespace yargs { (args: ReadonlyArray, cwd?: string): { [K in keyof Arguments]: Arguments[K] }; // Aliases for previously declared options can inherit the types of those options. - alias(shortName: K1, longName: K2): Argv; - alias(shortName: K2, longName: K1): Argv; + alias(shortName: K1, longName: K2 | ReadonlyArray): Argv; + alias(shortName: K2, longName: K1 | ReadonlyArray): Argv; alias(shortName: string | ReadonlyArray, longName: string | ReadonlyArray): Argv; alias(aliases: { [shortName: string]: string | ReadonlyArray }): Argv; diff --git a/types/yargs/yargs-tests.ts b/types/yargs/yargs-tests.ts index f32023ec99..0d2f1d5bcd 100644 --- a/types/yargs/yargs-tests.ts +++ b/types/yargs/yargs-tests.ts @@ -668,59 +668,19 @@ function Argv$scriptName() { } function Argv$inferOptionTypes() { - const argv1 = yargs - .option("u", { type: "string" }) - .alias("u", "url") + // $ExpectType { [x: string]: any; array: string[]; boolean: boolean; count: number; number: number; string: string; _: string[]; $0: string; } + yargs + .option("array", { type: "array" }) + .option("boolean", { type: "boolean" }) + .option("count", { type: "count" }) + .option("number", { type: "number" }) + .option("string", { type: "string" }) .argv; - // $ExpectType string - argv1.u; - // $ExpectType string - argv1.url; - - const argv2 = yargs - .option("verbose", { default: false }) - .alias("v", "verbose") - .argv; - - // $ExpectType boolean - argv2.v; - // $ExpectType boolean - argv2.verbose; - - const argv3 = yargs - .option({ - count: { number: true }, - date: { coerce: Date.parse } - }) - .alias("c", "count") - .argv; - - // $ExpectType number - argv3.c; - // $ExpectType number - argv3.count; - // $ExpectType number - argv3.date; - - const argv4 = yargs - .string(["u", "url"]) - .alias("url", "uri") - .argv; - - // $ExpectType string - argv4.u; - // $ExpectType string - argv4.url; - // $ExpectType string - argv4.uri; - // $ExpectType any - argv4.bogus; - type Color = "red" | "blue" | "green"; const colors: Color[] = ["red", "blue", "green"]; - const argv5 = yargs + const argv = yargs .array("array") .boolean("boolean") .choices("color", colors) @@ -732,19 +692,68 @@ function Argv$inferOptionTypes() { .argv; // $ExpectType string[] - argv5.array; + argv.array; // $ExpectType boolean - argv5.boolean; + argv.boolean; // $ExpectType Color - argv5.color; + argv.color; // $ExpectType number - argv5.count; + argv.count; // $ExpectType Date - argv5.date; + argv.date; // $ExpectType string - argv5.normalize; + argv.normalize; // $ExpectType number - argv5.number; + argv.number; // $ExpectType string - argv5.string; + argv.string; +} + +function Argv$fallbackToAnyForUnknownOptions() { + // $ExpectType any + yargs.argv.bogus; + + // $ExpectType any + yargs + .option({a: { type: "string" }, b: { type: "boolean" } }) + .argv + .bogus; +} + +function Argv$inferOptionTypesForAliases() { + // $ExpectType { [x: string]: any; u: string; url: string; _: string[]; $0: string; } + yargs + .option("u", { type: "string" }) + .alias("u", "url") + .argv; + + // $ExpectType { [x: string]: any; v: boolean; loud: boolean; noisy: boolean; verbose: boolean; n: boolean; _: string[]; $0: string; } + yargs + .option("v", { default: false }) + .alias("v", ["loud", "noisy", "verbose"]) + .alias("n", "noisy") + .argv; + + // $ExpectType { [x: string]: any; n: number; count: number; num: number; _: string[]; $0: string; } + yargs + .option("n", { number: true }) + .alias("n", "count") + .alias("num", ["n", "count"]) + .argv; +} + +function Argv$inferOptionTypesFromCoercions() { + // $ExpectType number + yargs + .option("date", { coerce: Date.parse }) + .argv + .date; + + // $ExpectType { [x: string]: any; date: number; factor: number; _: string[]; $0: string; } + yargs + .options({ + date: { coerce: Date.parse }, + factor: { coerce: parseFloat } + }) + .argv; } From d8787cebbae95cda2c83885a50aae85e97875902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Viken=20Valv=C3=A5g?= Date: Sat, 1 Dec 2018 17:58:54 +0100 Subject: [PATCH 06/17] Fix for count, more tests. --- types/yargs/index.d.ts | 1 + types/yargs/yargs-tests.ts | 77 +++++++++++++++++++++++++++----------- 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/types/yargs/index.d.ts b/types/yargs/index.d.ts index a4e9b6bce5..4710fb779c 100644 --- a/types/yargs/index.d.ts +++ b/types/yargs/index.d.ts @@ -327,6 +327,7 @@ declare namespace yargs { O extends { boolean: true } ? boolean : O extends { number: true } ? number : O extends { string: true } ? string : + O extends { count: true } ? number : O extends { normalize: true } ? string : O extends { choices: Array } ? C : O extends { default: infer D } ? D : diff --git a/types/yargs/yargs-tests.ts b/types/yargs/yargs-tests.ts index 0d2f1d5bcd..8e07580fdd 100644 --- a/types/yargs/yargs-tests.ts +++ b/types/yargs/yargs-tests.ts @@ -667,6 +667,9 @@ function Argv$scriptName() { .scriptName("my-script"); } +type Color = "red" | "blue" | "green"; +const colors: Color[] = ["red", "blue", "green"]; + function Argv$inferOptionTypes() { // $ExpectType { [x: string]: any; array: string[]; boolean: boolean; count: number; number: number; string: string; _: string[]; $0: string; } yargs @@ -677,15 +680,36 @@ function Argv$inferOptionTypes() { .option("string", { type: "string" }) .argv; - type Color = "red" | "blue" | "green"; - const colors: Color[] = ["red", "blue", "green"]; + // $ExpectType { [x: string]: any; a: number; b: boolean; c: string; _: string[]; $0: string; } + yargs + .option("a", { default: 42 }) + .option("b", { default: false }) + .option("c", { default: "tmp" }) + .argv; + + // $ExpectType { [x: string]: any; array: string[]; boolean: boolean; number: number; string: string; _: string[]; $0: string; } + yargs + .option("array", { array: true }) + .option("boolean", { boolean: true }) + .option("number", { number: true }) + .option("string", { string: true }) + .argv; + + // $ExpectType { [x: string]: any; choices: Color; coerce: Date; count: number; normalize: string; _: string[]; $0: string; } + yargs + .option("choices", { choices: colors }) + .option("coerce", { coerce: () => new Date() }) + .option("count", { count: true }) + .option("normalize", { normalize: true }) + .argv; const argv = yargs .array("array") .boolean("boolean") - .choices("color", colors) + .choices("choices", colors) + .coerce("coerce", Date.parse) .count("count") - .default("date", new Date()) + .default("default", new Date()) .normalize("normalize") .number("number") .string("string") @@ -696,11 +720,13 @@ function Argv$inferOptionTypes() { // $ExpectType boolean argv.boolean; // $ExpectType Color - argv.color; + argv.choices; + // $ExpectType number + argv.coerce; // $ExpectType number argv.count; // $ExpectType Date - argv.date; + argv.default; // $ExpectType string argv.normalize; // $ExpectType number @@ -709,6 +735,29 @@ function Argv$inferOptionTypes() { argv.string; } +function Argv$inferMultipleOptionTypes() { + // $ExpectType { [x: string]: any; a: string; b: boolean; c: number; d: number; e: number; _: string[]; $0: string; } + yargs + .option({ a: { type: "string" }, b: { type: "boolean" } }) + .number(["c", "d", "e"]) + .argv; + + // $ExpectType { [x: string]: any; a: number; b: string; c: boolean; _: string[]; $0: string; } + yargs + .default({ a: 42, b: "b", c: false }) + .argv; + + // $ExpectType { [x: string]: any; a: number; b: string; c: Date; _: string[]; $0: string; } + yargs + .coerce({ a: Date.parse, b: String.prototype.toLowerCase, c: (s: string) => new Date(s) }) + .argv; + + // $ExpectType { [x: string]: any; a: number; b: string; c: Color; _: string[]; $0: string; } + yargs + .choices({ a: [1, 2, 3], b: ["black", "white"], c: colors }) + .argv; +} + function Argv$fallbackToAnyForUnknownOptions() { // $ExpectType any yargs.argv.bogus; @@ -741,19 +790,3 @@ function Argv$inferOptionTypesForAliases() { .alias("num", ["n", "count"]) .argv; } - -function Argv$inferOptionTypesFromCoercions() { - // $ExpectType number - yargs - .option("date", { coerce: Date.parse }) - .argv - .date; - - // $ExpectType { [x: string]: any; date: number; factor: number; _: string[]; $0: string; } - yargs - .options({ - date: { coerce: Date.parse }, - factor: { coerce: parseFloat } - }) - .argv; -} From 0c54262bee24af492b175c36a4e57a7311c212c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Viken=20Valv=C3=A5g?= Date: Sat, 1 Dec 2018 18:05:16 +0100 Subject: [PATCH 07/17] More restructuring of tests. --- types/yargs/yargs-tests.ts | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/types/yargs/yargs-tests.ts b/types/yargs/yargs-tests.ts index 8e07580fdd..984c35300d 100644 --- a/types/yargs/yargs-tests.ts +++ b/types/yargs/yargs-tests.ts @@ -703,36 +703,32 @@ function Argv$inferOptionTypes() { .option("normalize", { normalize: true }) .argv; - const argv = yargs - .array("array") - .boolean("boolean") - .choices("choices", colors) - .coerce("coerce", Date.parse) - .count("count") - .default("default", new Date()) - .normalize("normalize") - .number("number") - .string("string") - .argv; - // $ExpectType string[] - argv.array; + yargs.array("x").argv.x; + // $ExpectType boolean - argv.boolean; + yargs.boolean("x").argv.x; + // $ExpectType Color - argv.choices; + yargs.choices("x", colors).argv.x; + // $ExpectType number - argv.coerce; + yargs.coerce("x", Date.parse).argv.x; + // $ExpectType number - argv.count; + yargs.count("x").argv.x; + // $ExpectType Date - argv.default; + yargs.default("x", new Date()).argv.x; + // $ExpectType string - argv.normalize; + yargs.normalize("x").argv.x; + // $ExpectType number - argv.number; + yargs.number("x").argv.x; + // $ExpectType string - argv.string; + yargs.string("x").argv.x; } function Argv$inferMultipleOptionTypes() { From f59869ebafc0a2fae42d91b46a357b5eb77b1fea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Viken=20Valv=C3=A5g?= Date: Sat, 1 Dec 2018 23:04:15 +0100 Subject: [PATCH 08/17] Handle optional vs. required options. --- types/yargs/index.d.ts | 38 ++++++++++----- types/yargs/yargs-tests.ts | 94 ++++++++++++++++++++++++++++---------- 2 files changed, 96 insertions(+), 36 deletions(-) diff --git a/types/yargs/index.d.ts b/types/yargs/index.d.ts index 4710fb779c..be5ffbd574 100644 --- a/types/yargs/index.d.ts +++ b/types/yargs/index.d.ts @@ -44,21 +44,21 @@ declare namespace yargs { argv: { [K in keyof Arguments]: Arguments[K] }; - array(key: K | ReadonlyArray): Argv; + array(key: K | ReadonlyArray): Argv; - boolean(key: K | ReadonlyArray): Argv; + boolean(key: K | ReadonlyArray): Argv; check(func: (argv: Arguments, aliases: { [alias: string]: string }) => any, global?: boolean): Argv; - choices>(key: K, values: C): Argv; - choices }>(choices: C): Argv; + choices>(key: K, values: C): Argv; + choices }>(choices: C): Argv; // For previously declared options, we can infer the parameter type of the coercion function. coerce(key: K | ReadonlyArray, func: (arg: T[K]) => V): Argv; coerce any }>(opts: O): Argv }>; - coerce(key: K | ReadonlyArray, func: (arg: any) => V): Argv; - coerce any }>(opts: O): Argv }>; + coerce(key: K | ReadonlyArray, func: (arg: any) => V): Argv; + coerce any }>(opts: O): Argv | undefined }>; command(command: string | ReadonlyArray, description: string, builder?: (args: Argv) => Argv, handler?: (args: Arguments) => void): Argv; command(command: string | ReadonlyArray, description: string, builder?: O, handler?: (args: Arguments>) => void): Argv; @@ -94,12 +94,14 @@ declare namespace yargs { * @deprecated since version 6.6.0 * Use '.demandCommand()' or '.demandOption()' instead */ + demand(key: K | ReadonlyArray, msg?: string | true): Argv>; demand(key: string | ReadonlyArray, msg: string): Argv; demand(key: string | ReadonlyArray, required?: boolean): Argv; demand(positionals: number, msg: string): Argv; demand(positionals: number, required?: boolean): Argv; demand(positionals: number, max: number, msg?: string): Argv; + demandOption(key: K | ReadonlyArray, msg?: string | true): Argv>; demandOption(key: string | ReadonlyArray, msg?: string): Argv; demandOption(key: string | ReadonlyArray, demand?: boolean): Argv; @@ -150,9 +152,9 @@ declare namespace yargs { nargs(key: string, count: number): Argv; nargs(nargs: { [key: string]: number }): Argv; - normalize(key: K | ReadonlyArray): Argv; + normalize(key: K | ReadonlyArray): Argv; - number(key: K | ReadonlyArray): Argv; + number(key: K | ReadonlyArray): Argv; option(key: K, options: O): Argv }>; option(options: O): Argv>; @@ -177,6 +179,7 @@ declare namespace yargs { * @deprecated since version 6.6.0 * Use '.demandCommand()' or '.demandOption()' instead */ + require(key: K | ReadonlyArray, msg?: string | true): Argv>; require(key: string, msg: string): Argv; require(key: string, required: boolean): Argv; require(keys: ReadonlyArray, msg: string): Argv; @@ -188,6 +191,7 @@ declare namespace yargs { * @deprecated since version 6.6.0 * Use '.demandCommand()' or '.demandOption()' instead */ + required(key: K | ReadonlyArray, msg?: string | true): Argv>; required(key: string, msg: string): Argv; required(key: string, required: boolean): Argv; required(keys: ReadonlyArray, msg: string): Argv; @@ -218,7 +222,7 @@ declare namespace yargs { strict(): Argv; - string(key: K | ReadonlyArray): Argv; + string(key: K | ReadonlyArray): Argv; // Intended to be used with '.wrap()' terminalWidth(): number; @@ -317,20 +321,30 @@ declare namespace yargs { type?: PositionalOptionsType; } + /* Remove undefined as a possible value for keys K in T */ + type Required = Pick> & { [key in K]: Exclude }; + type InferredOptionType = + O extends { default: infer D } ? D : + O extends { type: "count" } ? number : + O extends { count: true } ? number : + O extends { required: string | true } ? RequiredOptionType : + O extends { require: string | true } ? RequiredOptionType : + O extends { demand: string | true } ? RequiredOptionType : + O extends { demandOption: string | true } ? RequiredOptionType : + RequiredOptionType | undefined; + + type RequiredOptionType = O extends { type: "array" } ? string[] : O extends { type: "boolean" } ? boolean : O extends { type: "number" } ? number : O extends { type: "string" } ? string : - O extends { type: "count" } ? number : O extends { array: true } ? string[] : O extends { boolean: true } ? boolean : O extends { number: true } ? number : O extends { string: true } ? string : - O extends { count: true } ? number : O extends { normalize: true } ? string : O extends { choices: Array } ? C : - O extends { default: infer D } ? D : O extends { coerce: (arg: any) => infer T } ? T : any; diff --git a/types/yargs/yargs-tests.ts b/types/yargs/yargs-tests.ts index 984c35300d..33493c21d3 100644 --- a/types/yargs/yargs-tests.ts +++ b/types/yargs/yargs-tests.ts @@ -671,13 +671,13 @@ type Color = "red" | "blue" | "green"; const colors: Color[] = ["red", "blue", "green"]; function Argv$inferOptionTypes() { - // $ExpectType { [x: string]: any; array: string[]; boolean: boolean; count: number; number: number; string: string; _: string[]; $0: string; } + // $ExpectType { [x: string]: any; a: string[] | undefined; b: boolean | undefined; c: number; n: number | undefined; s: string | undefined; _: string[]; $0: string; } yargs - .option("array", { type: "array" }) - .option("boolean", { type: "boolean" }) - .option("count", { type: "count" }) - .option("number", { type: "number" }) - .option("string", { type: "string" }) + .option("a", { type: "array" }) + .option("b", { type: "boolean" }) + .option("c", { type: "count" }) + .option("n", { type: "number" }) + .option("s", { type: "string" }) .argv; // $ExpectType { [x: string]: any; a: number; b: boolean; c: string; _: string[]; $0: string; } @@ -687,32 +687,32 @@ function Argv$inferOptionTypes() { .option("c", { default: "tmp" }) .argv; - // $ExpectType { [x: string]: any; array: string[]; boolean: boolean; number: number; string: string; _: string[]; $0: string; } + // $ExpectType { [x: string]: any; a: string[] | undefined; b: boolean | undefined; n: number | undefined; s: string | undefined; _: string[]; $0: string; } yargs - .option("array", { array: true }) - .option("boolean", { boolean: true }) - .option("number", { number: true }) - .option("string", { string: true }) + .option("a", { array: true }) + .option("b", { boolean: true }) + .option("n", { number: true }) + .option("s", { string: true }) .argv; - // $ExpectType { [x: string]: any; choices: Color; coerce: Date; count: number; normalize: string; _: string[]; $0: string; } + // $ExpectType { [x: string]: any; choices: Color; coerce: Date | undefined; count: number; normalize: string | undefined; _: string[]; $0: string; } yargs - .option("choices", { choices: colors }) + .option("choices", { choices: colors, required: true }) .option("coerce", { coerce: () => new Date() }) .option("count", { count: true }) .option("normalize", { normalize: true }) .argv; - // $ExpectType string[] + // $ExpectType string[] | undefined yargs.array("x").argv.x; - // $ExpectType boolean + // $ExpectType boolean | undefined yargs.boolean("x").argv.x; - // $ExpectType Color + // $ExpectType "red" | "blue" | "green" | undefined yargs.choices("x", colors).argv.x; - // $ExpectType number + // $ExpectType number | undefined yargs.coerce("x", Date.parse).argv.x; // $ExpectType number @@ -721,21 +721,66 @@ function Argv$inferOptionTypes() { // $ExpectType Date yargs.default("x", new Date()).argv.x; - // $ExpectType string + // $ExpectType string | undefined yargs.normalize("x").argv.x; - // $ExpectType number + // $ExpectType number | undefined yargs.number("x").argv.x; - // $ExpectType string + // $ExpectType string | undefined yargs.string("x").argv.x; } +function Argv$inferRequiredOptionTypes() { + // $ExpectType string + yargs.string("x").demand("x").argv.x; + + // $ExpectType string + yargs.string("x").demandOption("x").argv.x; + + // $ExpectType string | undefined + yargs.string("x").demandOption("x", false).argv.x; + + // $ExpectType { [x: string]: any; x: string; y: number; _: string[]; $0: string; } + yargs.string("x").number("y").demandOption(["x", "y"]).argv; + + // $ExpectType { [x: string]: any; x: string; y: number; _: string[]; $0: string; } + yargs.string("x").number("y").demandOption(["x", "y"], true).argv; + + // $ExpectType { [x: string]: any; x: string | undefined; y: number | undefined; _: string[]; $0: string; } + yargs.string("x").number("y").demandOption(["x", "y"], false).argv; + + // $ExpectType string + yargs.option("x", { string: true, require: true }).argv.x; + + // $ExpectType string + yargs.option("x", { string: true, required: true }).argv.x; + + // $ExpectType string + yargs.option("x", { string: true, demand: true }).argv.x; + + // $ExpectType string + yargs.option("x", { string: true, demandOption: true }).argv.x; + + // $ExpectType string | undefined + yargs.option("x", { string: true, demandOption: false }).argv.x; + + // $ExpectType number + yargs.option("x", { count: true }).argv.x; + + // $ExpectType number + yargs.option("x", { number: true, default: 42 }).argv.x; + + // $ExpectType string[] + yargs.option("x", { array: true, demandOption: true }).argv.x; +} + function Argv$inferMultipleOptionTypes() { // $ExpectType { [x: string]: any; a: string; b: boolean; c: number; d: number; e: number; _: string[]; $0: string; } yargs - .option({ a: { type: "string" }, b: { type: "boolean" } }) + .option({ a: { default: "a" }, b: { default: false } }) .number(["c", "d", "e"]) + .demandOption(["c", "d", "e"]) .argv; // $ExpectType { [x: string]: any; a: number; b: string; c: boolean; _: string[]; $0: string; } @@ -748,9 +793,10 @@ function Argv$inferMultipleOptionTypes() { .coerce({ a: Date.parse, b: String.prototype.toLowerCase, c: (s: string) => new Date(s) }) .argv; - // $ExpectType { [x: string]: any; a: number; b: string; c: Color; _: string[]; $0: string; } + // $ExpectType { [x: string]: any; a: number | undefined; b: string | undefined; c: Color; _: string[]; $0: string; } yargs .choices({ a: [1, 2, 3], b: ["black", "white"], c: colors }) + .demandOption("c") .argv; } @@ -766,7 +812,7 @@ function Argv$fallbackToAnyForUnknownOptions() { } function Argv$inferOptionTypesForAliases() { - // $ExpectType { [x: string]: any; u: string; url: string; _: string[]; $0: string; } + // $ExpectType { [x: string]: any; u: string | undefined; url: string | undefined; _: string[]; $0: string; } yargs .option("u", { type: "string" }) .alias("u", "url") @@ -781,7 +827,7 @@ function Argv$inferOptionTypesForAliases() { // $ExpectType { [x: string]: any; n: number; count: number; num: number; _: string[]; $0: string; } yargs - .option("n", { number: true }) + .option("n", { number: true, demandOption: true }) .alias("n", "count") .alias("num", ["n", "count"]) .argv; From 3e4524e66ab994f4e0ef08b60306f91559e026ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Viken=20Valv=C3=A5g?= Date: Sat, 1 Dec 2018 23:10:12 +0100 Subject: [PATCH 09/17] Consistently use [key in ... for mapped types. --- types/yargs/index.d.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/types/yargs/index.d.ts b/types/yargs/index.d.ts index be5ffbd574..2babcf6b12 100644 --- a/types/yargs/index.d.ts +++ b/types/yargs/index.d.ts @@ -32,8 +32,8 @@ declare namespace yargs { // For the return type / argv property, we create a mapped type over // Arguments to simplify the inferred type signature in client code. interface Argv { - (): { [K in keyof Arguments]: Arguments[K] }; - (args: ReadonlyArray, cwd?: string): { [K in keyof Arguments]: Arguments[K] }; + (): { [key in keyof Arguments]: Arguments[key] }; + (args: ReadonlyArray, cwd?: string): { [key in keyof Arguments]: Arguments[key] }; // Aliases for previously declared options can inherit the types of those options. alias(shortName: K1, longName: K2 | ReadonlyArray): Argv; @@ -42,7 +42,7 @@ declare namespace yargs { alias(shortName: string | ReadonlyArray, longName: string | ReadonlyArray): Argv; alias(aliases: { [shortName: string]: string | ReadonlyArray }): Argv; - argv: { [K in keyof Arguments]: Arguments[K] }; + argv: { [key in keyof Arguments]: Arguments[key] }; array(key: K | ReadonlyArray): Argv; @@ -51,14 +51,14 @@ declare namespace yargs { check(func: (argv: Arguments, aliases: { [alias: string]: string }) => any, global?: boolean): Argv; choices>(key: K, values: C): Argv; - choices }>(choices: C): Argv; + choices }>(choices: C): Argv; // For previously declared options, we can infer the parameter type of the coercion function. coerce(key: K | ReadonlyArray, func: (arg: T[K]) => V): Argv; - coerce any }>(opts: O): Argv }>; + coerce any }>(opts: O): Argv }>; coerce(key: K | ReadonlyArray, func: (arg: any) => V): Argv; - coerce any }>(opts: O): Argv | undefined }>; + coerce any }>(opts: O): Argv | undefined }>; command(command: string | ReadonlyArray, description: string, builder?: (args: Argv) => Argv, handler?: (args: Arguments) => void): Argv; command(command: string | ReadonlyArray, description: string, builder?: O, handler?: (args: Arguments>) => void): Argv; @@ -348,7 +348,7 @@ declare namespace yargs { O extends { coerce: (arg: any) => infer T } ? T : any; - type InferredOptionTypes = { [K in keyof O]: InferredOptionType }; + type InferredOptionTypes = { [key in keyof O]: InferredOptionType }; interface CommandModule { aliases?: ReadonlyArray | string; From 6473debf344d892663cf7ae7a68d4343b185297b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Viken=20Valv=C3=A5g?= Date: Sat, 1 Dec 2018 23:34:17 +0100 Subject: [PATCH 10/17] Improve handling of arrays, defaulting to (string | number)[]. --- types/yargs/index.d.ts | 14 +++++++-- types/yargs/yargs-tests.ts | 62 +++++++++++++++++++++++++++++--------- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/types/yargs/index.d.ts b/types/yargs/index.d.ts index 2babcf6b12..3be62e433b 100644 --- a/types/yargs/index.d.ts +++ b/types/yargs/index.d.ts @@ -44,7 +44,7 @@ declare namespace yargs { argv: { [key in keyof Arguments]: Arguments[key] }; - array(key: K | ReadonlyArray): Argv; + array(key: K | ReadonlyArray): Argv | undefined }>; boolean(key: K | ReadonlyArray): Argv; @@ -335,11 +335,19 @@ declare namespace yargs { RequiredOptionType | undefined; type RequiredOptionType = - O extends { type: "array" } ? string[] : + O extends { type: "array", string: true } ? string[] : + O extends { type: "array", number: true } ? number[] : + O extends { type: "array", normalize: true } ? string[] : + O extends { type: "string", array: true } ? string[] : + O extends { type: "number", array: true } ? number[] : + O extends { string: true, array: true } ? string[] : + O extends { number: true, array: true } ? number[] : + O extends { normalize: true, array: true } ? string[] : + O extends { type: "array" } ? Array : O extends { type: "boolean" } ? boolean : O extends { type: "number" } ? number : O extends { type: "string" } ? string : - O extends { array: true } ? string[] : + O extends { array: true } ? Array : O extends { boolean: true } ? boolean : O extends { number: true } ? number : O extends { string: true } ? string : diff --git a/types/yargs/yargs-tests.ts b/types/yargs/yargs-tests.ts index 33493c21d3..6f3e77e14a 100644 --- a/types/yargs/yargs-tests.ts +++ b/types/yargs/yargs-tests.ts @@ -671,7 +671,7 @@ type Color = "red" | "blue" | "green"; const colors: Color[] = ["red", "blue", "green"]; function Argv$inferOptionTypes() { - // $ExpectType { [x: string]: any; a: string[] | undefined; b: boolean | undefined; c: number; n: number | undefined; s: string | undefined; _: string[]; $0: string; } + // $ExpectType { [x: string]: any; a: (string | number)[] | undefined; b: boolean | undefined; c: number; n: number | undefined; s: string | undefined; _: string[]; $0: string; } yargs .option("a", { type: "array" }) .option("b", { type: "boolean" }) @@ -687,7 +687,7 @@ function Argv$inferOptionTypes() { .option("c", { default: "tmp" }) .argv; - // $ExpectType { [x: string]: any; a: string[] | undefined; b: boolean | undefined; n: number | undefined; s: string | undefined; _: string[]; $0: string; } + // $ExpectType { [x: string]: any; a: (string | number)[] | undefined; b: boolean | undefined; n: number | undefined; s: string | undefined; _: string[]; $0: string; } yargs .option("a", { array: true }) .option("b", { boolean: true }) @@ -703,7 +703,7 @@ function Argv$inferOptionTypes() { .option("normalize", { normalize: true }) .argv; - // $ExpectType string[] | undefined + // $ExpectType (string | number)[] | undefined yargs.array("x").argv.x; // $ExpectType boolean | undefined @@ -771,7 +771,7 @@ function Argv$inferRequiredOptionTypes() { // $ExpectType number yargs.option("x", { number: true, default: 42 }).argv.x; - // $ExpectType string[] + // $ExpectType (string | number)[] yargs.option("x", { array: true, demandOption: true }).argv.x; } @@ -800,17 +800,6 @@ function Argv$inferMultipleOptionTypes() { .argv; } -function Argv$fallbackToAnyForUnknownOptions() { - // $ExpectType any - yargs.argv.bogus; - - // $ExpectType any - yargs - .option({a: { type: "string" }, b: { type: "boolean" } }) - .argv - .bogus; -} - function Argv$inferOptionTypesForAliases() { // $ExpectType { [x: string]: any; u: string | undefined; url: string | undefined; _: string[]; $0: string; } yargs @@ -832,3 +821,46 @@ function Argv$inferOptionTypesForAliases() { .alias("num", ["n", "count"]) .argv; } + +function Argv$inferArrayOptionTypes() { + // $ExpectType (string | number)[] + yargs.option("a", { array: true, demandOption: true }).argv.a; + + // $ExpectType string[] + yargs.option("a", { array: true, string: true, demandOption: true }).argv.a; + + // $ExpectType number[] + yargs.option("a", { array: true, number: true, demandOption: true }).argv.a; + + // $ExpectType string[] + yargs.option("a", { array: true, normalize: true, demandOption: true }).argv.a; + + // $ExpectType string[] | undefined + yargs.option("a", { array: true, type: "string" }).argv.a; + + // $ExpectType number[] | undefined + yargs.option("a", { array: true, type: "number" }).argv.a; + + // $ExpectType string[] | undefined + yargs.option("a", { array: true, normalize: true }).argv.a; + + // $ExpectType string[] | undefined + yargs.option("a", { string: true, type: "array" }).argv.a; + + // $ExpectType number[] | undefined + yargs.option("a", { number: true, type: "array" }).argv.a; + + // $ExpectType string[] | undefined + yargs.option("a", { normalize: true, type: "array" }).argv.a; +} + +function Argv$fallbackToAnyForUnknownOptions() { + // $ExpectType any + yargs.argv.bogus; + + // $ExpectType any + yargs + .option({a: { type: "string" }, b: { type: "boolean" } }) + .argv + .bogus; +} From 0aee094572011b575f449baf70630a616742dce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Viken=20Valv=C3=A5g?= Date: Sun, 2 Dec 2018 00:56:18 +0100 Subject: [PATCH 11/17] More improvements to array/number/string handling, and use Omit to reset types for repeated options. --- types/yargs/index.d.ts | 64 ++++++++++++++++++++++++-------------- types/yargs/yargs-tests.ts | 61 +++++++++++++++++++++++++++++++++++- 2 files changed, 100 insertions(+), 25 deletions(-) diff --git a/types/yargs/index.d.ts b/types/yargs/index.d.ts index 3be62e433b..ac8dbbb749 100644 --- a/types/yargs/index.d.ts +++ b/types/yargs/index.d.ts @@ -44,21 +44,22 @@ declare namespace yargs { argv: { [key in keyof Arguments]: Arguments[key] }; - array(key: K | ReadonlyArray): Argv | undefined }>; + array(key: K | ReadonlyArray): Argv & { [key in K]: ToArray }>; + array(key: K | ReadonlyArray): Argv & { [key in K]: Array | undefined }>; - boolean(key: K | ReadonlyArray): Argv; + boolean(key: K | ReadonlyArray): Argv & { [key in K]: boolean | undefined }>; check(func: (argv: Arguments, aliases: { [alias: string]: string }) => any, global?: boolean): Argv; - choices>(key: K, values: C): Argv; - choices }>(choices: C): Argv; + choices>(key: K, values: C): Argv & { [key in K]: C[number] | undefined }>; + choices }>(choices: C): Argv & { [key in keyof C]: C[key][number] | undefined }>; // For previously declared options, we can infer the parameter type of the coercion function. - coerce(key: K | ReadonlyArray, func: (arg: T[K]) => V): Argv; - coerce any }>(opts: O): Argv }>; + coerce(key: K | ReadonlyArray, func: (arg: T[K]) => V): Argv & { [key in K]: V }>; + coerce any }>(opts: O): Argv & { [key in keyof O]: ReturnType }>; - coerce(key: K | ReadonlyArray, func: (arg: any) => V): Argv; - coerce any }>(opts: O): Argv | undefined }>; + coerce(key: K | ReadonlyArray, func: (arg: any) => V): Argv & { [key in K]: V | undefined }>; + coerce any }>(opts: O): Argv & { [key in keyof O]: ReturnType | undefined }>; command(command: string | ReadonlyArray, description: string, builder?: (args: Argv) => Argv, handler?: (args: Arguments) => void): Argv; command(command: string | ReadonlyArray, description: string, builder?: O, handler?: (args: Arguments>) => void): Argv; @@ -85,23 +86,23 @@ declare namespace yargs { conflicts(key: string, value: string | ReadonlyArray): Argv; conflicts(conflicts: { [key: string]: string | ReadonlyArray }): Argv; - count(key: K | ReadonlyArray): Argv; + count(key: K | ReadonlyArray): Argv & { [key in K]: number }>; - default(key: K, value: V, description?: string): Argv; - default(defaults: D, description?: string): Argv; + default(key: K, value: V, description?: string): Argv & { [key in K]: V }>; + default(defaults: D, description?: string): Argv & D>; /** * @deprecated since version 6.6.0 * Use '.demandCommand()' or '.demandOption()' instead */ - demand(key: K | ReadonlyArray, msg?: string | true): Argv>; + demand(key: K | ReadonlyArray, msg?: string | true): Argv>; demand(key: string | ReadonlyArray, msg: string): Argv; demand(key: string | ReadonlyArray, required?: boolean): Argv; demand(positionals: number, msg: string): Argv; demand(positionals: number, required?: boolean): Argv; demand(positionals: number, max: number, msg?: string): Argv; - demandOption(key: K | ReadonlyArray, msg?: string | true): Argv>; + demandOption(key: K | ReadonlyArray, msg?: string | true): Argv>; demandOption(key: string | ReadonlyArray, msg?: string): Argv; demandOption(key: string | ReadonlyArray, demand?: boolean): Argv; @@ -152,15 +153,17 @@ declare namespace yargs { nargs(key: string, count: number): Argv; nargs(nargs: { [key: string]: number }): Argv; - normalize(key: K | ReadonlyArray): Argv; + normalize(key: K | ReadonlyArray): Argv & { [key in K]: ToString }>; + normalize(key: K | ReadonlyArray): Argv & { [key in K]: string | undefined }>; - number(key: K | ReadonlyArray): Argv; + number(key: K | ReadonlyArray): Argv & { [key in K]: ToNumber }>; + number(key: K | ReadonlyArray): Argv & { [key in K]: number | undefined }>; - option(key: K, options: O): Argv }>; - option(options: O): Argv>; + option(key: K, options: O): Argv & { [key in K]: InferredOptionType }>; + option(options: O): Argv & InferredOptionTypes>; - options(key: K, options: O): Argv }>; - options(options: O): Argv>; + options(key: K, options: O): Argv & { [key in K]: InferredOptionType }>; + options(options: O): Argv & InferredOptionTypes>; parse(): Arguments; parse(arg: string | ReadonlyArray, context?: object, parseCallback?: ParseCallback): Arguments; @@ -179,7 +182,7 @@ declare namespace yargs { * @deprecated since version 6.6.0 * Use '.demandCommand()' or '.demandOption()' instead */ - require(key: K | ReadonlyArray, msg?: string | true): Argv>; + require(key: K | ReadonlyArray, msg?: string | true): Argv>; require(key: string, msg: string): Argv; require(key: string, required: boolean): Argv; require(keys: ReadonlyArray, msg: string): Argv; @@ -191,7 +194,7 @@ declare namespace yargs { * @deprecated since version 6.6.0 * Use '.demandCommand()' or '.demandOption()' instead */ - required(key: K | ReadonlyArray, msg?: string | true): Argv>; + required(key: K | ReadonlyArray, msg?: string | true): Argv>; required(key: string, msg: string): Argv; required(key: string, required: boolean): Argv; required(keys: ReadonlyArray, msg: string): Argv; @@ -222,7 +225,8 @@ declare namespace yargs { strict(): Argv; - string(key: K | ReadonlyArray): Argv; + string(key: K | ReadonlyArray): Argv & { [key in K]: ToString }>; + string(key: K | ReadonlyArray): Argv & { [key in K]: string | undefined }>; // Intended to be used with '.wrap()' terminalWidth(): number; @@ -321,8 +325,20 @@ declare namespace yargs { type?: PositionalOptionsType; } - /* Remove undefined as a possible value for keys K in T */ - type Required = Pick> & { [key in K]: Exclude }; + /** Remove keys K in T */ + type Omit = Pick>; + + /** Remove undefined as a possible value for keys K in T */ + type Defined = Omit & { [key in K]: Exclude }; + + /** Convert T to T[] and T | undefined to T[] | undefined */ + type ToArray = Array> | Extract; + + /** Gives string[] | undefined or string[] if T is an array type, otherwise string */ + type ToString = T extends any[] ? string[] : T extends any[] | undefined ? string[] | undefined : string | undefined; + + /** Gives number[] | undefined or number[] if T is an array type, otherwise number */ + type ToNumber = T extends any[] ? number[] : T extends any[] | undefined ? number[] | undefined : number | undefined; type InferredOptionType = O extends { default: infer D } ? D : diff --git a/types/yargs/yargs-tests.ts b/types/yargs/yargs-tests.ts index 6f3e77e14a..688082e523 100644 --- a/types/yargs/yargs-tests.ts +++ b/types/yargs/yargs-tests.ts @@ -695,7 +695,7 @@ function Argv$inferOptionTypes() { .option("s", { string: true }) .argv; - // $ExpectType { [x: string]: any; choices: Color; coerce: Date | undefined; count: number; normalize: string | undefined; _: string[]; $0: string; } + // $ExpectType { [x: string]: any; count: number; choices: Color; coerce: Date | undefined; normalize: string | undefined; _: string[]; $0: string; } yargs .option("choices", { choices: colors, required: true }) .option("coerce", { coerce: () => new Date() }) @@ -852,6 +852,65 @@ function Argv$inferArrayOptionTypes() { // $ExpectType string[] | undefined yargs.option("a", { normalize: true, type: "array" }).argv.a; + + // $ExpectType string[] | undefined + yargs.string("a").array("a").argv.a; + + // $ExpectType string[] | undefined + yargs.array("a").string("a").argv.a; + + // $ExpectType string[] + yargs.string("a").array("a").demandOption("a").argv.a; + + // $ExpectType string[] + yargs.array("a").string("a").demandOption("a").argv.a; + + // $ExpectType string[] + yargs.string("a").demandOption("a").array("a").argv.a; + + // $ExpectType string[] + yargs.array("a").demandOption("a").string("a").argv.a; + + // $ExpectType number[] + yargs.number("a").array("a").demandOption("a").argv.a; + + // $ExpectType number[] + yargs.array("a").number("a").demandOption("a").argv.a; + + // $ExpectType number[] + yargs.array("a").demandOption("a").number("a").argv.a; + + // $ExpectType string[] + yargs.normalize("a").array("a").demandOption("a").argv.a; + + // $ExpectType string[] + yargs.array("a").normalize("a").demandOption("a").argv.a; + + // $ExpectType string[] + yargs.array("a").demandOption("a").normalize("a").argv.a; +} + +function Argv$usesTheLastInferredOptionType() { + // $ExpectType boolean | undefined + yargs.string("a").boolean("a").argv.a; + + // FIXME: $ExpectType string | undefined + // FIXME: yargs.number("a").string("a").argv.a; + + // FIXME: $ExpectType number | undefined + // FIXME: yargs.string("a").number("a").argv.a; + + // $ExpectType boolean | undefined + yargs.string("a").option("a", { number: true }).boolean("a").argv.a; + + // $ExpectType boolean | undefined + yargs.number("a").option("a", { string: true }).boolean("a").argv.a; + + // $ExpectType string | undefined + yargs.boolean("a").option("a", { number: true }).option("a", { string: true }).argv.a; + + // $ExpectType number | undefined + yargs.boolean("a").option("a", { string: true }).option("a", { number: true }).argv.a; } function Argv$fallbackToAnyForUnknownOptions() { From 370789b525c65708c28e6bde075e5f15a3d9cecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Viken=20Valv=C3=A5g?= Date: Sun, 2 Dec 2018 23:17:16 +0100 Subject: [PATCH 12/17] More precise usage of Omit, fix ToString and ToNumber. --- types/yargs/index.d.ts | 43 ++++++++++++++++++++------------------ types/yargs/yargs-tests.ts | 20 ++++++++++++------ 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/types/yargs/index.d.ts b/types/yargs/index.d.ts index ac8dbbb749..48fda7ea16 100644 --- a/types/yargs/index.d.ts +++ b/types/yargs/index.d.ts @@ -45,20 +45,19 @@ declare namespace yargs { argv: { [key in keyof Arguments]: Arguments[key] }; array(key: K | ReadonlyArray): Argv & { [key in K]: ToArray }>; - array(key: K | ReadonlyArray): Argv & { [key in K]: Array | undefined }>; + array(key: K | ReadonlyArray): Argv | undefined }>; - boolean(key: K | ReadonlyArray): Argv & { [key in K]: boolean | undefined }>; + boolean(key: K | ReadonlyArray): Argv & { [key in K]: boolean | undefined }>; + boolean(key: K | ReadonlyArray): Argv; check(func: (argv: Arguments, aliases: { [alias: string]: string }) => any, global?: boolean): Argv; - choices>(key: K, values: C): Argv & { [key in K]: C[number] | undefined }>; + choices>(key: K, values: C): Argv & { [key in K]: C[number] | undefined }>; + choices>(key: K, values: C): Argv; choices }>(choices: C): Argv & { [key in keyof C]: C[key][number] | undefined }>; - // For previously declared options, we can infer the parameter type of the coercion function. - coerce(key: K | ReadonlyArray, func: (arg: T[K]) => V): Argv & { [key in K]: V }>; - coerce any }>(opts: O): Argv & { [key in keyof O]: ReturnType }>; - - coerce(key: K | ReadonlyArray, func: (arg: any) => V): Argv & { [key in K]: V | undefined }>; + coerce(key: K | ReadonlyArray, func: (arg: any) => V): Argv & { [key in K]: V | undefined }>; + coerce(key: K | ReadonlyArray, func: (arg: any) => V): Argv; coerce any }>(opts: O): Argv & { [key in keyof O]: ReturnType | undefined }>; command(command: string | ReadonlyArray, description: string, builder?: (args: Argv) => Argv, handler?: (args: Arguments) => void): Argv; @@ -86,9 +85,11 @@ declare namespace yargs { conflicts(key: string, value: string | ReadonlyArray): Argv; conflicts(conflicts: { [key: string]: string | ReadonlyArray }): Argv; - count(key: K | ReadonlyArray): Argv & { [key in K]: number }>; + count(key: K | ReadonlyArray): Argv & { [key in K]: number }>; + count(key: K | ReadonlyArray): Argv; - default(key: K, value: V, description?: string): Argv & { [key in K]: V }>; + default(key: K, value: V, description?: string): Argv & { [key in K]: V }>; + default(key: K, value: V, description?: string): Argv; default(defaults: D, description?: string): Argv & D>; /** @@ -154,15 +155,17 @@ declare namespace yargs { nargs(nargs: { [key: string]: number }): Argv; normalize(key: K | ReadonlyArray): Argv & { [key in K]: ToString }>; - normalize(key: K | ReadonlyArray): Argv & { [key in K]: string | undefined }>; + normalize(key: K | ReadonlyArray): Argv; number(key: K | ReadonlyArray): Argv & { [key in K]: ToNumber }>; - number(key: K | ReadonlyArray): Argv & { [key in K]: number | undefined }>; + number(key: K | ReadonlyArray): Argv; - option(key: K, options: O): Argv & { [key in K]: InferredOptionType }>; + option(key: K, options: O): Argv & { [key in K]: InferredOptionType }>; + option(key: K, options: O): Argv }>; option(options: O): Argv & InferredOptionTypes>; - options(key: K, options: O): Argv & { [key in K]: InferredOptionType }>; + options(key: K, options: O): Argv & { [key in K]: InferredOptionType }>; + options(key: K, options: O): Argv }>; options(options: O): Argv & InferredOptionTypes>; parse(): Arguments; @@ -226,7 +229,7 @@ declare namespace yargs { strict(): Argv; string(key: K | ReadonlyArray): Argv & { [key in K]: ToString }>; - string(key: K | ReadonlyArray): Argv & { [key in K]: string | undefined }>; + string(key: K | ReadonlyArray): Argv; // Intended to be used with '.wrap()' terminalWidth(): number; @@ -326,7 +329,7 @@ declare namespace yargs { } /** Remove keys K in T */ - type Omit = Pick>; + type Omit = { [key in Exclude]: T[key] }; /** Remove undefined as a possible value for keys K in T */ type Defined = Omit & { [key in K]: Exclude }; @@ -334,11 +337,11 @@ declare namespace yargs { /** Convert T to T[] and T | undefined to T[] | undefined */ type ToArray = Array> | Extract; - /** Gives string[] | undefined or string[] if T is an array type, otherwise string */ - type ToString = T extends any[] ? string[] : T extends any[] | undefined ? string[] | undefined : string | undefined; + /** Gives string[] if T is an array type, otherwise string. Preserves | undefined. */ + type ToString = (Exclude extends any[] ? string[] : string) | Extract; - /** Gives number[] | undefined or number[] if T is an array type, otherwise number */ - type ToNumber = T extends any[] ? number[] : T extends any[] | undefined ? number[] | undefined : number | undefined; + /** Gives number[] if T is an array type, otherwise number. Preserves | undefined. */ + type ToNumber = (Exclude extends any[] ? number[] : number) | Extract; type InferredOptionType = O extends { default: infer D } ? D : diff --git a/types/yargs/yargs-tests.ts b/types/yargs/yargs-tests.ts index 688082e523..78006d2c6e 100644 --- a/types/yargs/yargs-tests.ts +++ b/types/yargs/yargs-tests.ts @@ -695,7 +695,7 @@ function Argv$inferOptionTypes() { .option("s", { string: true }) .argv; - // $ExpectType { [x: string]: any; count: number; choices: Color; coerce: Date | undefined; normalize: string | undefined; _: string[]; $0: string; } + // $ExpectType { [x: string]: any; choices: Color; coerce: Date | undefined; count: number; normalize: string | undefined; _: string[]; $0: string; } yargs .option("choices", { choices: colors, required: true }) .option("coerce", { coerce: () => new Date() }) @@ -783,6 +783,13 @@ function Argv$inferMultipleOptionTypes() { .demandOption(["c", "d", "e"]) .argv; + // $ExpectType { [x: string]: any; a: string; b: boolean; c: number; d: number; e: number; _: string[]; $0: string; } + yargs + .options({ a: { default: "a" }, b: { default: false } }) + .number(["c", "d", "e"]) + .demandOption(["c", "d", "e"]) + .argv; + // $ExpectType { [x: string]: any; a: number; b: string; c: boolean; _: string[]; $0: string; } yargs .default({ a: 42, b: "b", c: false }) @@ -791,6 +798,7 @@ function Argv$inferMultipleOptionTypes() { // $ExpectType { [x: string]: any; a: number; b: string; c: Date; _: string[]; $0: string; } yargs .coerce({ a: Date.parse, b: String.prototype.toLowerCase, c: (s: string) => new Date(s) }) + .demandOption(["a", "b", "c"]) .argv; // $ExpectType { [x: string]: any; a: number | undefined; b: string | undefined; c: Color; _: string[]; $0: string; } @@ -890,15 +898,15 @@ function Argv$inferArrayOptionTypes() { yargs.array("a").demandOption("a").normalize("a").argv.a; } -function Argv$usesTheLastInferredOptionType() { +function Argv$inferRepeatedOptionTypes() { // $ExpectType boolean | undefined yargs.string("a").boolean("a").argv.a; - // FIXME: $ExpectType string | undefined - // FIXME: yargs.number("a").string("a").argv.a; + // $ExpectType string | undefined + yargs.number("a").string("a").argv.a; - // FIXME: $ExpectType number | undefined - // FIXME: yargs.string("a").number("a").argv.a; + // $ExpectType number | undefined + yargs.string("a").number("a").argv.a; // $ExpectType boolean | undefined yargs.string("a").option("a", { number: true }).boolean("a").argv.a; From f65661bf4095842da2fb02375bbf950eb2bd423b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Viken=20Valv=C3=A5g?= Date: Sun, 2 Dec 2018 23:27:13 +0100 Subject: [PATCH 13/17] Remove blank line, keeping the alias overloads in one group.. --- types/yargs/index.d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/types/yargs/index.d.ts b/types/yargs/index.d.ts index 48fda7ea16..321fb3669b 100644 --- a/types/yargs/index.d.ts +++ b/types/yargs/index.d.ts @@ -38,7 +38,6 @@ declare namespace yargs { // Aliases for previously declared options can inherit the types of those options. alias(shortName: K1, longName: K2 | ReadonlyArray): Argv; alias(shortName: K2, longName: K1 | ReadonlyArray): Argv; - alias(shortName: string | ReadonlyArray, longName: string | ReadonlyArray): Argv; alias(aliases: { [shortName: string]: string | ReadonlyArray }): Argv; From 2854b92db0b2be63fe6ccfb4d0abcdf5fed4df31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Viken=20Valv=C3=A5g?= Date: Sun, 2 Dec 2018 23:37:46 +0100 Subject: [PATCH 14/17] Bump required TypeScript version in yargs-parser to match yargs, and adapt the Arguments type. --- types/yargs-parser/index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/yargs-parser/index.d.ts b/types/yargs-parser/index.d.ts index d50a3e9903..cf448e841c 100644 --- a/types/yargs-parser/index.d.ts +++ b/types/yargs-parser/index.d.ts @@ -2,12 +2,12 @@ // Project: https://github.com/yargs/yargs-parser#readme // Definitions by: Miles Johnson // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// TypeScript Version: 2.2 +// TypeScript Version: 2.8 import { Arguments as YargsArguments } from 'yargs'; declare namespace yargsParser { - type Arguments = YargsArguments; + type Arguments = YargsArguments<{}>; interface DetailedArguments { argv: Arguments; From eb769a40abc07fe8fb5ec54d9aef0634dd808a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Viken=20Valv=C3=A5g?= Date: Mon, 3 Dec 2018 00:05:18 +0100 Subject: [PATCH 15/17] Use ReadonlyArray for choices. --- types/yargs/index.d.ts | 4 ++-- types/yargs/yargs-tests.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/types/yargs/index.d.ts b/types/yargs/index.d.ts index 321fb3669b..fd933bc78c 100644 --- a/types/yargs/index.d.ts +++ b/types/yargs/index.d.ts @@ -370,7 +370,7 @@ declare namespace yargs { O extends { number: true } ? number : O extends { string: true } ? string : O extends { normalize: true } ? string : - O extends { choices: Array } ? C : + O extends { choices: ReadonlyArray } ? C : O extends { coerce: (arg: any) => infer T } ? T : any; @@ -389,7 +389,7 @@ declare namespace yargs { type SyncCompletionFunction = (current: string, argv: any) => string[]; type AsyncCompletionFunction = (current: string, argv: any, done: (completion: ReadonlyArray) => void) => void; type MiddlewareFunction = (args: Arguments) => void; - type Choices = Array; + type Choices = ReadonlyArray; type PositionalOptionsType = "boolean" | "number" | "string"; } diff --git a/types/yargs/yargs-tests.ts b/types/yargs/yargs-tests.ts index 78006d2c6e..3a580400c2 100644 --- a/types/yargs/yargs-tests.ts +++ b/types/yargs/yargs-tests.ts @@ -668,7 +668,7 @@ function Argv$scriptName() { } type Color = "red" | "blue" | "green"; -const colors: Color[] = ["red", "blue", "green"]; +const colors: ReadonlyArray = ["red", "blue", "green"]; function Argv$inferOptionTypes() { // $ExpectType { [x: string]: any; a: (string | number)[] | undefined; b: boolean | undefined; c: number; n: number | undefined; s: string | undefined; _: string[]; $0: string; } From a0601f9802252a4f0a11690385ad556c360f8a6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Viken=20Valv=C3=A5g?= Date: Fri, 21 Dec 2018 08:45:09 +0100 Subject: [PATCH 16/17] yargs: Fall back to unknown for unknown arguments, and enable demand/demandOption to come before the option definition. Still fall back to any in yargs-parser (removing the dependency from yargs-parser on yargs). --- types/yargs-parser/index.d.ts | 13 +++++--- types/yargs/index.d.ts | 12 ++++---- types/yargs/yargs-tests.ts | 57 +++++++++++++++++++++++------------ 3 files changed, 52 insertions(+), 30 deletions(-) diff --git a/types/yargs-parser/index.d.ts b/types/yargs-parser/index.d.ts index cf448e841c..a53abb3b5b 100644 --- a/types/yargs-parser/index.d.ts +++ b/types/yargs-parser/index.d.ts @@ -2,12 +2,17 @@ // Project: https://github.com/yargs/yargs-parser#readme // Definitions by: Miles Johnson // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// TypeScript Version: 2.8 - -import { Arguments as YargsArguments } from 'yargs'; +// TypeScript Version: 2.2 declare namespace yargsParser { - type Arguments = YargsArguments<{}>; + interface Arguments { + /** Non-option arguments */ + _: string[]; + /** The script name or node command */ + $0: string; + /** All remaining options */ + [argName: string]: any; + } interface DetailedArguments { argv: Arguments; diff --git a/types/yargs/index.d.ts b/types/yargs/index.d.ts index fd933bc78c..db741dca67 100644 --- a/types/yargs/index.d.ts +++ b/types/yargs/index.d.ts @@ -8,7 +8,7 @@ // Teddy Cross // Steffen Viken Valvåg // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// TypeScript Version: 2.8 +// TypeScript Version: 3.0 // The following TSLint rules have been disabled: // unified-signatures: Because there is useful information in the argument names of the overloaded signatures @@ -27,7 +27,7 @@ declare namespace yargs { // The type parameter T is the expected shape of the parsed options. // Arguments is those options plus _ and $0, and an indexer falling - // back to any for unknown options. + // back to unknown for unknown options. // // For the return type / argv property, we create a mapped type over // Arguments to simplify the inferred type signature in client code. @@ -96,14 +96,14 @@ declare namespace yargs { * Use '.demandCommand()' or '.demandOption()' instead */ demand(key: K | ReadonlyArray, msg?: string | true): Argv>; - demand(key: string | ReadonlyArray, msg: string): Argv; + demand(key: K | ReadonlyArray, msg?: string | true): Argv; demand(key: string | ReadonlyArray, required?: boolean): Argv; demand(positionals: number, msg: string): Argv; demand(positionals: number, required?: boolean): Argv; demand(positionals: number, max: number, msg?: string): Argv; demandOption(key: K | ReadonlyArray, msg?: string | true): Argv>; - demandOption(key: string | ReadonlyArray, msg?: string): Argv; + demandOption(key: K | ReadonlyArray, msg?: string | true): Argv; demandOption(key: string | ReadonlyArray, demand?: boolean): Argv; demandCommand(): Argv; @@ -258,7 +258,7 @@ declare namespace yargs { /** The script name or node command */ $0: string; /** All remaining options */ - [argName: string]: any; + [argName: string]: unknown; }; interface RequireDirectoryOptions { @@ -372,7 +372,7 @@ declare namespace yargs { O extends { normalize: true } ? string : O extends { choices: ReadonlyArray } ? C : O extends { coerce: (arg: any) => infer T } ? T : - any; + unknown; type InferredOptionTypes = { [key in keyof O]: InferredOptionType }; diff --git a/types/yargs/yargs-tests.ts b/types/yargs/yargs-tests.ts index 3a580400c2..67fc312570 100644 --- a/types/yargs/yargs-tests.ts +++ b/types/yargs/yargs-tests.ts @@ -14,7 +14,7 @@ const stringVal = 'string'; function xup() { const argv = yargs.argv; - if (argv.rif - 5 * argv.xup > 7.138) { + if (typeof argv.rif === "number" && typeof argv.xup === "number" && argv.rif - 5 * argv.xup > 7.138) { console.log('Plunder more riffiwobbles!'); } else { console.log('Drop the xupptumblers!'); @@ -47,6 +47,7 @@ function divide() { const argv = yargs .usage('Usage: $0 -x [num] -y [num]') .demand(['x', 'y']) + .number(['x', 'y']) .argv; console.log(argv.x / argv.y); @@ -121,7 +122,7 @@ function Argv$argv() { function Argv_parsing() { const argv1 = yargs.parse(); - const argv2 = yargs(['-x', '1', '-y', '2']).argv; + const argv2 = yargs(['-x', '1', '-y', '2']); const argv3 = yargs.parse(['-x', '1', '-y', '2']); console.log(argv1.x, argv2.x, argv3.x); } @@ -671,7 +672,7 @@ type Color = "red" | "blue" | "green"; const colors: ReadonlyArray = ["red", "blue", "green"]; function Argv$inferOptionTypes() { - // $ExpectType { [x: string]: any; a: (string | number)[] | undefined; b: boolean | undefined; c: number; n: number | undefined; s: string | undefined; _: string[]; $0: string; } + // $ExpectType { [x: string]: unknown; a: (string | number)[] | undefined; b: boolean | undefined; c: number; n: number | undefined; s: string | undefined; _: string[]; $0: string; } yargs .option("a", { type: "array" }) .option("b", { type: "boolean" }) @@ -680,14 +681,14 @@ function Argv$inferOptionTypes() { .option("s", { type: "string" }) .argv; - // $ExpectType { [x: string]: any; a: number; b: boolean; c: string; _: string[]; $0: string; } + // $ExpectType { [x: string]: unknown; a: number; b: boolean; c: string; _: string[]; $0: string; } yargs .option("a", { default: 42 }) .option("b", { default: false }) .option("c", { default: "tmp" }) .argv; - // $ExpectType { [x: string]: any; a: (string | number)[] | undefined; b: boolean | undefined; n: number | undefined; s: string | undefined; _: string[]; $0: string; } + // $ExpectType { [x: string]: unknown; a: (string | number)[] | undefined; b: boolean | undefined; n: number | undefined; s: string | undefined; _: string[]; $0: string; } yargs .option("a", { array: true }) .option("b", { boolean: true }) @@ -695,7 +696,7 @@ function Argv$inferOptionTypes() { .option("s", { string: true }) .argv; - // $ExpectType { [x: string]: any; choices: Color; coerce: Date | undefined; count: number; normalize: string | undefined; _: string[]; $0: string; } + // $ExpectType { [x: string]: unknown; choices: Color; coerce: Date | undefined; count: number; normalize: string | undefined; _: string[]; $0: string; } yargs .option("choices", { choices: colors, required: true }) .option("coerce", { coerce: () => new Date() }) @@ -735,21 +736,33 @@ function Argv$inferRequiredOptionTypes() { // $ExpectType string yargs.string("x").demand("x").argv.x; + // $ExpectType string + yargs.demand("x").string("x").argv.x; + // $ExpectType string yargs.string("x").demandOption("x").argv.x; // $ExpectType string | undefined yargs.string("x").demandOption("x", false).argv.x; - // $ExpectType { [x: string]: any; x: string; y: number; _: string[]; $0: string; } + // $ExpectType { [x: string]: unknown; x: string; y: number; _: string[]; $0: string; } yargs.string("x").number("y").demandOption(["x", "y"]).argv; - // $ExpectType { [x: string]: any; x: string; y: number; _: string[]; $0: string; } + // $ExpectType { [x: string]: unknown; x: string; y: number; _: string[]; $0: string; } yargs.string("x").number("y").demandOption(["x", "y"], true).argv; - // $ExpectType { [x: string]: any; x: string | undefined; y: number | undefined; _: string[]; $0: string; } + // $ExpectType { [x: string]: unknown; x: string | undefined; y: number | undefined; _: string[]; $0: string; } yargs.string("x").number("y").demandOption(["x", "y"], false).argv; + // $ExpectType { [x: string]: unknown; x: string; y: number; _: string[]; $0: string; } + yargs.demandOption(["x", "y"]).string("x").number("y").argv; + + // $ExpectType { [x: string]: unknown; x: string; y: number; _: string[]; $0: string; } + yargs.demandOption(["x", "y"], true).string("x").number("y").argv; + + // $ExpectType { [x: string]: unknown; x: string | undefined; y: number | undefined; _: string[]; $0: string; } + yargs.demandOption(["x", "y"], false).string("x").number("y").argv; + // $ExpectType string yargs.option("x", { string: true, require: true }).argv.x; @@ -776,32 +789,32 @@ function Argv$inferRequiredOptionTypes() { } function Argv$inferMultipleOptionTypes() { - // $ExpectType { [x: string]: any; a: string; b: boolean; c: number; d: number; e: number; _: string[]; $0: string; } + // $ExpectType { [x: string]: unknown; a: string; b: boolean; c: number; d: number; e: number; _: string[]; $0: string; } yargs .option({ a: { default: "a" }, b: { default: false } }) .number(["c", "d", "e"]) .demandOption(["c", "d", "e"]) .argv; - // $ExpectType { [x: string]: any; a: string; b: boolean; c: number; d: number; e: number; _: string[]; $0: string; } + // $ExpectType { [x: string]: unknown; a: string; b: boolean; c: number; d: number; e: number; _: string[]; $0: string; } yargs .options({ a: { default: "a" }, b: { default: false } }) .number(["c", "d", "e"]) .demandOption(["c", "d", "e"]) .argv; - // $ExpectType { [x: string]: any; a: number; b: string; c: boolean; _: string[]; $0: string; } + // $ExpectType { [x: string]: unknown; a: number; b: string; c: boolean; _: string[]; $0: string; } yargs .default({ a: 42, b: "b", c: false }) .argv; - // $ExpectType { [x: string]: any; a: number; b: string; c: Date; _: string[]; $0: string; } + // $ExpectType { [x: string]: unknown; a: number; b: string; c: Date; _: string[]; $0: string; } yargs .coerce({ a: Date.parse, b: String.prototype.toLowerCase, c: (s: string) => new Date(s) }) .demandOption(["a", "b", "c"]) .argv; - // $ExpectType { [x: string]: any; a: number | undefined; b: string | undefined; c: Color; _: string[]; $0: string; } + // $ExpectType { [x: string]: unknown; a: number | undefined; b: string | undefined; c: Color; _: string[]; $0: string; } yargs .choices({ a: [1, 2, 3], b: ["black", "white"], c: colors }) .demandOption("c") @@ -809,20 +822,20 @@ function Argv$inferMultipleOptionTypes() { } function Argv$inferOptionTypesForAliases() { - // $ExpectType { [x: string]: any; u: string | undefined; url: string | undefined; _: string[]; $0: string; } + // $ExpectType { [x: string]: unknown; u: string | undefined; url: string | undefined; _: string[]; $0: string; } yargs .option("u", { type: "string" }) .alias("u", "url") .argv; - // $ExpectType { [x: string]: any; v: boolean; loud: boolean; noisy: boolean; verbose: boolean; n: boolean; _: string[]; $0: string; } + // $ExpectType { [x: string]: unknown; v: boolean; loud: boolean; noisy: boolean; verbose: boolean; n: boolean; _: string[]; $0: string; } yargs .option("v", { default: false }) .alias("v", ["loud", "noisy", "verbose"]) .alias("n", "noisy") .argv; - // $ExpectType { [x: string]: any; n: number; count: number; num: number; _: string[]; $0: string; } + // $ExpectType { [x: string]: unknown; n: number; count: number; num: number; _: string[]; $0: string; } yargs .option("n", { number: true, demandOption: true }) .alias("n", "count") @@ -921,13 +934,17 @@ function Argv$inferRepeatedOptionTypes() { yargs.boolean("a").option("a", { string: true }).option("a", { number: true }).argv.a; } -function Argv$fallbackToAnyForUnknownOptions() { - // $ExpectType any +function Argv$fallbackToUnknownForUnknownOptions() { + // $ExpectType unknown yargs.argv.bogus; - // $ExpectType any + // $ExpectType unknown yargs .option({a: { type: "string" }, b: { type: "boolean" } }) .argv .bogus; + + // $ExpectError + const x: string = yargs.argv.x; + return x; } From 4b5873e6f7368258d9c3c4b6b9a9f3b3da2fba81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Viken=20Valv=C3=A5g?= Date: Sat, 22 Dec 2018 10:01:47 +0100 Subject: [PATCH 17/17] More tests for falling back to unknown. --- types/yargs/yargs-tests.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/types/yargs/yargs-tests.ts b/types/yargs/yargs-tests.ts index 67fc312570..a76015dcfc 100644 --- a/types/yargs/yargs-tests.ts +++ b/types/yargs/yargs-tests.ts @@ -944,6 +944,14 @@ function Argv$fallbackToUnknownForUnknownOptions() { .argv .bogus; + const argv = yargs.option({ a: {}, b: {} }).option("c", {}).argv; + // $ExpectType unknown + argv.a; + // $ExpectType unknown + argv.b; + // $ExpectType unknown + argv.c; + // $ExpectError const x: string = yargs.argv.x; return x;