Skip to content

Commit

Permalink
Expand typescript checks (#1198)
Browse files Browse the repository at this point in the history
* Add proof of concept Command type without option properties

* Add program

* Add tests for exported global, and start refactoring existing tests

* Methodically test syntax, instead of just lots of README and example code
  • Loading branch information
shadowspawn committed Feb 21, 2020
1 parent a3f453f commit 3cf8cff
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 141 deletions.
308 changes: 167 additions & 141 deletions typings/commander-tests.ts
@@ -1,161 +1,187 @@
import * as program from './index';

interface ExtendedOptions extends program.CommandOptions {
isNew: any;
}

const commandInstance = new program.Command('-f');
const optionsInstance = new program.Option('-f');
const errorInstance = new program.CommanderError(1, 'code', 'message');

const name = program.name();
import * as commander from './index';
import { Script } from 'vm';

// Test Commander usage with TypeScript.
// This is NOT a usable program, just used to test for compile errors!

// Defined stricter type, as the options as properties `[key: string]: any`
// makes the type checking very weak.
// https://github.com/Microsoft/TypeScript/issues/25987#issuecomment-441224690
type KnownKeys<T> = {
[K in keyof T]: string extends K ? never : number extends K ? never : K
} extends {[_ in keyof T]: infer U} ? ({} extends U ? never : U) : never;
type CommandWithoutOptionsAsProperties = Pick<commander.Command, KnownKeys<commander.Command>>;

const program: CommandWithoutOptionsAsProperties = commander.program;
const programWithOptions = commander.program;
// program.silly; // <-- Error, hurrah!

// Check for exported global Command objects
const importedDefaultProgram: commander.Command = commander;
const importedExplicitProgram: commander.Command = commander.program;

// Check export classes exist
const commandInstance1 = new commander.Command();
const commandInstance2 = new commander.Command('name');
const optionsInstance = new commander.Option('-f');
const errorInstance = new commander.CommanderError(1, 'code', 'message');

// Command properties
console.log(programWithOptions.someOption);
console.log(programWithOptions['someOption']);
const theArgs = program.args;
const theCommands: commander.Command[] = program.commands;

// version
const versionThis1: commander.Command = program.version('1.2.3');
const versionThis2: commander.Command = program.version('1.2.3', '-r,--revision');
const versionThis3: commander.Command = program.version('1.2.3', '-r,--revision', 'show revision information');

// command (and CommandOptions)
const commandNew1: commander.Command = program.command('action');
const commandNew2: commander.Command = program.command('action', { isDefault: true, noHelp: true });
const commandThis1: commander.Command = program.command('exec', 'exec description');
const commandThis2: commander.Command = program.command('exec', 'exec description', { isDefault: true, noHelp: true, executableFile: 'foo' });

// addCommand
const addCommandThis: commander.Command = program.addCommand(new commander.Command('abc'));

// arguments
const argumentsThis: commander.Command = program.arguments('<cmd> [env]')

// exitOverride
const exitThis1: commander.Command = program.exitOverride();
const exitThis2: commander.Command = program.exitOverride((err):never => {
return process.exit(err.exitCode);
});
const exitThis3: commander.Command = program.exitOverride((err):void => {
if (err.code !== 'commander.executeSubCommandAsync') {
throw err;
} else {
// Async callback from spawn events, not useful to throw.
}
});

program.storeOptionsAsProperties(true);
program.passCommandToAction(true);
// action
const actionThis1: commander.Command = program.action(() => { });
const actionThis2: commander.Command = program.action(async () => { });

program
.name('set name')
.version('0.0.1')
.option('-p, --peppers', 'Add peppers')
.option('-P, --pineapple', 'Add pineapple')
.option('-b, --bbq', 'Add bbq sauce')
.option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')
.parse(process.argv);
// option
const optionThis1: commander.Command = program.option('-a,--alpha');
const optionThis2: commander.Command = program.option('-p, --peppers', 'Add peppers')
const optionThis3: commander.Command = program.option('-s, --string [value]', 'default string', 'value')
const optionThis4: commander.Command = program.option('-b, --boolean', 'default boolean', false);

console.log('you ordered a pizza with:');
if (program['peppers']) console.log(' - peppers');
if (program['pineapple']) console.log(' - pineapple');
if (program['bbq']) console.log(' - bbq');
console.log(' - %s cheese', program['cheese']);
// example coercion functions from README

function range(val: string) {
return val.split('..').map(Number);
return val.split('..').map(Number);
}

function list(val: string) {
return val.split(',');
function myParseInt(value: string, dummyPrevious: number) {
return parseInt(value);
}

function collect(val: string, memo: string[]) {
memo.push(val);
return memo;
function increaseVerbosity(dummyValue: string, previous: number) {
return previous + 1;
}

function increaseVerbosity(v: any, total: number) {
return total + 1;
function collect(value: string, previous: string[]) {
return previous.concat([value]);
}

function syncCall() {
console.log("Sync success!");
function commaSeparatedList(value: string, dummyPrevious: string[]) {
return value.split(',');
}

async function asyncCall() {
return;
}

program
.version('0.0.1')
.usage('[options] <file ...>')
.option('-i, --integer <n>', 'An integer argument', parseInt)
.option('-f, --float <n>', 'A float argument', parseFloat)
.option('-r, --range <a>..<b>', 'A range', range)
.option('-l, --list <items>', 'A list', list)
.option('-o, --optional [value]', 'An optional value')
.option('-c, --collect [value]', 'A repeatable value', collect, [])
.option('-v, --verbose', 'A value that can be increased', increaseVerbosity, 0)
.parse(process.argv);

console.log(' int: %j', program['integer']);
console.log(' float: %j', program['float']);
console.log(' optional: %j', program['optional']);
program['range'] = program['range'] || [];
console.log(' range: %j..%j', program['range'][0], program['range'][1]);
console.log(' list: %j', program['list']);
console.log(' collect: %j', program['collect']);
console.log(' verbosity: %j', program['verbose']);
console.log(' args: %j', program['args']);

program
.version('0.0.1')
.option('-f, --foo', 'enable some foo')
.option('-b, --bar', 'enable some bar')
.option('-B, --baz', 'enable some baz');

// must be before .parse()
program.on('--help', () => {
console.log(' Examples:');
console.log('');
console.log(' $ custom-help --help');
console.log(' $ custom-help -h');
console.log('');
});

program
.command('allow-unknown-option')
.description("description")
.allowUnknownOption()
.action(() => {
console.log('unknown option is allowed');
});

program
.requiredOption('-a,--aaa', 'description')
.requiredOption('-b,--bbb <value>', 'description')
.requiredOption('-c,--ccc [value]', 'description')
.requiredOption('-d,--ddd <value>', 'description', 'default value')
.requiredOption('-e,--eee <value>', 'description', (value, memo) => { return value; })
.requiredOption('-f,--fff <value>', 'description', (value, memo) => { return value; }, 'starting value')
.requiredOption('-g,--ggg <value>', 'description')
.requiredOption('-G,--no-ggg <value>', 'description for negation');

program
.version('0.0.1')
.arguments('<cmd> [env]')
.action((cmd, env) => {
console.log(cmd, env);
});

program
.command("name1", "description")
.command("name2", "description", { isDefault:true });

program
.command("name3").action(syncCall)
.command("name4").action(asyncCall);

const preparedCommand = new program.Command('prepared');
program.addCommand(preparedCommand);

program
.exitOverride();

program.exitOverride((err):never => {
console.log(err.code);
console.log(err.message);
console.log(err.nestedError);
return process.exit(err.exitCode);
});
const optionThis5: commander.Command = program.option('-f, --float <number>', 'float argument', parseFloat)
const optionThis6: commander.Command = program.option('-f, --float <number>', 'float argument', parseFloat, 3.2)
const optionThis7: commander.Command = program.option('-i, --integer <number>', 'integer argument', myParseInt)
const optionThis8: commander.Command = program.option('-i, --integer <number>', 'integer argument', myParseInt, 5)
const optionThis9: commander.Command = program.option('-v, --verbose', 'verbosity that can be increased', increaseVerbosity, 0)
const optionThis10: commander.Command = program.option('-c, --collect <value>', 'repeatable value', collect, [])
const optionThis11: commander.Command = program.option('-l, --list <items>', 'comma separated list', commaSeparatedList);

// requiredOption, same tests as option
const requiredOptionThis1: commander.Command = program.requiredOption('-a,--alpha');
const requiredOptionThis2: commander.Command = program.requiredOption('-p, --peppers', 'Add peppers')
const requiredOptionThis3: commander.Command = program.requiredOption('-s, --string [value]', 'default string', 'value')
const requiredOptionThis4: commander.Command = program.requiredOption('-b, --boolean', 'default boolean', false);

const requiredOptionThis5: commander.Command = program.requiredOption('-f, --float <number>', 'float argument', parseFloat)
const requiredOptionThis6: commander.Command = program.requiredOption('-f, --float <number>', 'float argument', parseFloat, 3.2)
const requiredOptionThis7: commander.Command = program.requiredOption('-i, --integer <number>', 'integer argument', myParseInt)
const requiredOptionThis8: commander.Command = program.requiredOption('-i, --integer <number>', 'integer argument', myParseInt, 5)
const requiredOptionThis9: commander.Command = program.requiredOption('-v, --verbose', 'verbosity that can be increased', increaseVerbosity, 0)
const requiredOptionThis10: commander.Command = program.requiredOption('-c, --collect <value>', 'repeatable value', collect, [])
const requiredOptionThis11: commander.Command = program.requiredOption('-l, --list <items>', 'comma separated list', commaSeparatedList);

// storeOptionsAsProperties
const storeOptionsAsPropertiesThis1: commander.Command = program.storeOptionsAsProperties();
const storeOptionsAsPropertiesThis2: commander.Command = program.storeOptionsAsProperties(false);

// passCommandToAction
const passCommandToActionThis1: commander.Command = program.passCommandToAction();
const passCommandToActionThis2: commander.Command = program.passCommandToAction(false);

// allowUnknownOption
const allowUnknownOptionThis1: commander.Command = program.allowUnknownOption();
const allowUnknownOptionThis2: commander.Command = program.allowUnknownOption(false);

// parse
const parseThis1: commander.Command = program.parse();
const parseThis2: commander.Command = program.parse(process.argv);
const parseThis3: commander.Command = program.parse(['node', 'script.js'], { from: 'node' });
const parseThis4: commander.Command = program.parse(['node', 'script.js'], { from: 'electron' });
const parseThis5: commander.Command = program.parse(['--option'], { from: "user" });

// parseAsync, same tests as parse
const parseAsyncThis1: Promise<commander.Command> = program.parseAsync();
const parseAsyncThis2: Promise<commander.Command> = program.parseAsync(process.argv);
const parseAsyncThis3: Promise<commander.Command> = program.parseAsync(['node', 'script.js'], { from: 'node' });
const parseAsyncThis4: Promise<commander.Command> = program.parseAsync(['node', 'script.js'], { from: 'electron' });
const parseAsyncThis5: Promise<commander.Command> = program.parseAsync(['--option'], { from: "user" });

// parseOptions (and ParseOptionsResult)
const { operands, unknown } = program.parseOptions(['node', 'script.js', 'hello']);

// opts
const opts = program.opts();
const optsVal1 = opts.foo;
const opstVale2 = opts['bar'];

// description
const descriptionThis: commander.Command = program.description("my description");
const descriptionValue: string = program.description();

// alias
const aliasThis: commander.Command = program.alias("my alias");
const aliasValue: string = program.alias();

// usage
const usageThis: commander.Command = program.usage("my usage");
const usageValue: string = program.usage();

// name
const nameThis: commander.Command = program.name("my-name");
const nameValue: string = program.name();

// outputHelp
program.outputHelp();
program.outputHelp((str: string) => { return str });

program.exitOverride((err):void => {
if (err.code !== 'commander.executeSubCommandAsync') {
throw err;
} else {
// Async callback from spawn events, not useful to throw.
}
});
// help
program.help();
program.help((str: string) => { return str });

program.parse(process.argv);
program.parse();
program.parse(["foo"], { from: "user" });

program.parseAsync(process.argv).then(() => {
console.log('parseAsync success');
}).catch(err => {
console.log('parseAsync failed');
});
// helpInformation
const helpInformnationValue: string = program.helpInformation();

program.help();
program.outputHelp();
const info = program.helpInformation();
// helpOption
const helpOptionThis1: commander.Command = program.helpOption('-h,--help');
const helpOptionThis2: commander.Command = program.helpOption('-h,--help', 'custom description');
const helpOptionThis3: commander.Command = program.helpOption(undefined, 'custom description');

console.log('stuff');
// on
const onThis: commander.Command = program.on('--help', () => { })
1 change: 1 addition & 0 deletions typings/index.d.ts
Expand Up @@ -346,6 +346,7 @@ declare namespace commander {
}

interface CommanderStatic extends Command {
program: Command;
Command: CommandConstructor;
Option: OptionConstructor;
CommanderError:CommanderErrorConstructor;
Expand Down

0 comments on commit 3cf8cff

Please sign in to comment.