diff --git a/README.md b/README.md index 9d5bc35..5fbbef1 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ This package offers TypeScript typings for `commander` which infer strong types - all the parameters of the action handler, including the options - options returned by `.opts()` +This package requires TypeScript 5.0 or higher (for `const` type parameters to `.choices()`). + The runtime is supplied by commander. This package is all about the typings. Usage @@ -62,12 +64,3 @@ const program = new Command() .option('-d, --debug'); // program type includes chained options and arguments const options = program.opts(); // smart type ``` - -Use a "const assertion" on the choices to narrow the option type from `string`: - -```typescript -const program = new Command() - .addOption(new Option('--drink-size ').choices(['small', 'medium', 'large'] as const)) - .parse(); -const drinkSize = program.opts().drinkSize; // "small" | "medium" | "large" | undefined -``` diff --git a/index.d.ts b/index.d.ts index b529fb5..1e1cc37 100644 --- a/index.d.ts +++ b/index.d.ts @@ -387,7 +387,7 @@ export class Argument< /** * Set the default value, and optionally supply the description to be displayed in the help. */ - default( + default( value: T, description?: string, ): Argument; @@ -402,7 +402,7 @@ export class Argument< /** * Only allow argument value to be one of choices. */ - choices( + choices( values: T, ): Argument; // setting CoerceT to undefined because choices overrides argParser @@ -448,7 +448,7 @@ export class Option< /** * Set the default value, and optionally supply the description to be displayed in the help. */ - default( + default( value: T, description?: string, ): Option; @@ -463,7 +463,9 @@ export class Option< * new Option('--donate [amount]').preset('20').argParser(parseFloat); * ``` */ - preset(arg: T): Option; + preset( + arg: T, + ): Option; /** * Add option name(s) that conflict with this option. @@ -519,7 +521,7 @@ export class Option< /** * Only allow option value to be one of choices. */ - choices( + choices( values: T, ): Option; // setting CoerceT to undefined becuase choices overrides argParser diff --git a/tests/arguments.test-d.ts b/tests/arguments.test-d.ts index 28bc6a4..f8ece0d 100644 --- a/tests/arguments.test-d.ts +++ b/tests/arguments.test-d.ts @@ -153,7 +153,7 @@ program // mixed types possible, but unusual program.addArgument(new Argument('[foo]').default(3)).action((foo, options) => { - expectType(foo); + expectType(foo); expectAssignable(options); }); @@ -247,28 +247,28 @@ program.command('sub3 [bar]').action((bar, options) => { // choices program - .addArgument(new Argument('').choices(['A', 'B'] as const)) + .addArgument(new Argument('').choices(['A', 'B'])) .action((foo, options) => { expectType<'A' | 'B'>(foo); expectAssignable(options); }); program - .addArgument(new Argument('[foo]').choices(['A', 'B'] as const)) + .addArgument(new Argument('[foo]').choices(['A', 'B'])) .action((foo, options) => { expectType<'A' | 'B' | undefined>(foo); expectAssignable(options); }); program - .addArgument(new Argument('').choices(['A', 'B'] as const)) + .addArgument(new Argument('').choices(['A', 'B'])) .action((foo, options) => { expectType<('A' | 'B')[]>(foo); expectAssignable(options); }); program - .addArgument(new Argument('[foo...]').choices(['A', 'B'] as const)) + .addArgument(new Argument('[foo...]').choices(['A', 'B'])) .action((foo, options) => { expectType<('A' | 'B')[]>(foo); expectAssignable(options); @@ -286,57 +286,44 @@ program // default type ignored when arg is required expectType<'C'>( - program - .addArgument( - new Argument('').default('D' as const).choices(['C'] as const), - ) - .parse().processedArgs[0], + program.addArgument(new Argument('').default('D').choices(['C'])).parse() + .processedArgs[0], ); // default before choices results in union when arg optional expectType<'C' | 'D'>( - program - .addArgument( - new Argument('[foo]').default('D' as const).choices(['C'] as const), - ) - .parse().processedArgs[0], + program.addArgument(new Argument('[foo]').default('D').choices(['C'])).parse() + .processedArgs[0], ); // default after choices is still union type expectType<'C' | 'D'>( - program - .addArgument( - new Argument('[foo]').choices(['C'] as const).default('D' as const), - ) - .parse().processedArgs[0], + program.addArgument(new Argument('[foo]').choices(['C']).default('D')).parse() + .processedArgs[0], ); // argRequired after choices still narrows type expectType<'C'>( - program - .addArgument(new Argument('foo').choices(['C'] as const).argRequired()) - .parse().processedArgs[0], + program.addArgument(new Argument('foo').choices(['C']).argRequired()).parse() + .processedArgs[0], ); // argRequired before choices still narrows type expectType<'C'>( - program - .addArgument(new Argument('foo').argRequired().choices(['C'] as const)) - .parse().processedArgs[0], + program.addArgument(new Argument('foo').argRequired().choices(['C'])).parse() + .processedArgs[0], ); // argOptional after choices narrows type and includes undefined expectType<'C' | undefined>( - program - .addArgument(new Argument('foo').choices(['C'] as const).argOptional()) - .parse().processedArgs[0], + program.addArgument(new Argument('foo').choices(['C']).argOptional()).parse() + .processedArgs[0], ); // argOptional before choices narrows type and includes undefined expectType<'C' | undefined>( - program - .addArgument(new Argument('foo').argOptional().choices(['C'] as const)) - .parse().processedArgs[0], + program.addArgument(new Argument('foo').argOptional().choices(['C'])).parse() + .processedArgs[0], ); // argParser after choices overrides choice type @@ -344,7 +331,7 @@ expectType( program .addArgument( new Argument('') - .choices(['C'] as const) + .choices(['C']) .argParser((val: string, prev: number) => prev + Number.parseInt(val)), ) .parse().processedArgs[0], @@ -356,7 +343,7 @@ expectType<'C'>( .addArgument( new Argument('') .argParser((val: string, prev: number) => prev + Number.parseInt(val)) - .choices(['C'] as const), + .choices(['C']), ) .parse().processedArgs[0], ); diff --git a/tests/create-argument.test-d.ts b/tests/create-argument.test-d.ts index af67b53..de4a904 100644 --- a/tests/create-argument.test-d.ts +++ b/tests/create-argument.test-d.ts @@ -55,7 +55,7 @@ import { Command, createArgument } from '..'; { const program = new Command(); program - .addArgument(createArgument('').choices(['A', 'B', 'C'] as const)) + .addArgument(createArgument('').choices(['A', 'B', 'C'])) .action((arg) => { expectType<'A' | 'B' | 'C'>(arg); }); @@ -65,7 +65,7 @@ import { Command, createArgument } from '..'; { const program = new Command(); program - .addArgument(createArgument('').choices(['A', 'B', 'C'] as const)) + .addArgument(createArgument('').choices(['A', 'B', 'C'])) .action((arg) => { expectType<('A' | 'B' | 'C')[]>(arg); }); diff --git a/tests/create-option.test-d.ts b/tests/create-option.test-d.ts index 5632b7d..4f9a07d 100644 --- a/tests/create-option.test-d.ts +++ b/tests/create-option.test-d.ts @@ -62,11 +62,7 @@ import { Command, createOption } from '..'; const program = new Command(); const foo = program .addOption( - createOption('-f, --foo ', 'description').choices([ - 'A', - 'B', - 'C', - ] as const), + createOption('-f, --foo ', 'description').choices(['A', 'B', 'C']), ) .opts().foo; expectType<'A' | 'B' | 'C' | undefined>(foo); @@ -78,7 +74,7 @@ import { Command, createOption } from '..'; const foo = program .addOption( createOption('-f, --foo ', 'description') - .choices(['A', 'B', 'C'] as const) + .choices(['A', 'B', 'C']) .makeOptionMandatory(), ) .opts().foo; diff --git a/tests/options.test-d.ts b/tests/options.test-d.ts index e171d4e..8200443 100644 --- a/tests/options.test-d.ts +++ b/tests/options.test-d.ts @@ -93,7 +93,7 @@ const o20 = program .addOption(new Option('-c, --colour').default(0).preset(BigInt(3))) .addOption(new Option('-C, --no-colour').preset('on')) .opts(); -expectType<{ colour: string | number | bigint }>(o20); +expectType<{ colour: 'on' | 0 | bigint }>(o20); // multiple @@ -194,35 +194,33 @@ expectType<{ debug?: true }>(us7); // narrows required value to given choices const co1 = program - .addOption(new Option('-d, --debug ').choices(['A', 'B'] as const)) + .addOption(new Option('-d, --debug ').choices(['A', 'B'])) .opts(); expectType<{ debug?: 'A' | 'B' }>(co1); // narrows optional value to union of given choices and true const co2 = program - .addOption(new Option('-d, --debug [val]').choices(['A', 'B'] as const)) + .addOption(new Option('-d, --debug [val]').choices(['A', 'B'])) .opts(); expectType<{ debug?: 'A' | 'B' | true }>(co2); // narrows required option to given choices const co3 = program .addOption( - new Option('-d, --debug ') - .choices(['A', 'B'] as const) - .makeOptionMandatory(), + new Option('-d, --debug ').choices(['A', 'B']).makeOptionMandatory(), ) .opts(); expectType<{ debug: 'A' | 'B' }>(co3); // narrows variadic value to choices array const co4 = program - .addOption(new Option('-d, --debug ').choices(['A', 'B'] as const)) + .addOption(new Option('-d, --debug ').choices(['A', 'B'])) .opts(); expectType<{ debug?: ('A' | 'B')[] }>(co4); // narrows optional variadic value to choices | true array const co5 = program - .addOption(new Option('-d, --debug [val...]').choices(['A', 'B'] as const)) + .addOption(new Option('-d, --debug [val...]').choices(['A', 'B'])) .opts(); expectType<{ debug?: ('A' | 'B')[] | true }>(co5); @@ -230,7 +228,7 @@ expectType<{ debug?: ('A' | 'B')[] | true }>(co5); const co6 = program .addOption( new Option('-d, --debug [val...]') - .choices(['A', 'B'] as const) + .choices(['A', 'B']) .makeOptionMandatory(), ) .opts(); @@ -240,7 +238,7 @@ expectType<{ debug: ('A' | 'B')[] | true }>(co6); const co7 = program .addOption( new Option('-d, --debug ') - .choices(['A', 'B'] as const) + .choices(['A', 'B']) .makeOptionMandatory(), ) .opts(); @@ -248,24 +246,18 @@ expectType<{ debug: ('A' | 'B')[] }>(co7); // default before choices creates union type const co8 = program - .addOption( - new Option('--foo ').default('D' as const).choices(['C'] as const), - ) + .addOption(new Option('--foo ').default('D').choices(['C'])) .opts(); expectType<{ foo: 'C' | 'D' }>(co8); // default after choices creates union type const co9 = program - .addOption( - new Option('--foo ').choices(['C'] as const).default('D' as const), - ) + .addOption(new Option('--foo ').choices(['C']).default('D')) .opts(); expectType<{ foo: 'C' | 'D' }>(co9); // make mandatory before choices makes option mandatory const c10 = program - .addOption( - new Option('--foo ').makeOptionMandatory().choices(['C'] as const), - ) + .addOption(new Option('--foo ').makeOptionMandatory().choices(['C'])) .opts(); expectType<{ foo: 'C' }>(c10);