diff --git a/index.js b/index.js index e73254f7f..6fd72cd52 100644 --- a/index.js +++ b/index.js @@ -1461,6 +1461,29 @@ class Command extends EventEmitter { return width; }; + /** + * Convert value to a string for help + * + * @param {number|object|null} value + * @return {string} + */ + + _stringify(value) { + if (typeof value === 'object' && value !== null) { + // For arrays, simply execute this same method + if (Array.isArray(value)) { + return value.map((item) => this._stringify(item)).join(','); + } + + // Use the `toString` method for objects that don't use the default + if (typeof value.toString === 'function' && Object.getPrototypeOf(value).toString !== Object.prototype.toString) { + return value.toString(); + } + } + + return JSON.stringify(value); + } + /** * Return help for options. * @@ -1479,7 +1502,7 @@ class Command extends EventEmitter { // Explicit options (including version) const help = this.options.map((option) => { const fullDesc = option.description + - ((!option.negate && option.defaultValue !== undefined) ? ' (default: ' + JSON.stringify(option.defaultValue) + ')' : ''); + ((!option.negate && option.defaultValue !== undefined) ? ' (default: ' + this._stringify(option.defaultValue) + ')' : ''); return padOptionDetails(option.flags, fullDesc); }); diff --git a/tests/command.help.test.js b/tests/command.help.test.js index d6e0e899a..ed4258391 100644 --- a/tests/command.help.test.js +++ b/tests/command.help.test.js @@ -130,3 +130,62 @@ test('when both help flags masked then not displayed in helpInformation', () => const helpInformation = program.helpInformation(); expect(helpInformation).not.toMatch('display help'); }); + +test('when default value is object with custom toString then custom string displays in helpInformation', () => { + const program = new commander.Command(); + program + .option('--host ', 'select host', new URL('http://example.com/')); + const helpInformation = program.helpInformation(); + expect(helpInformation).toContain('default: http://example.com/'); +}); + +test('when default value is object without custom toString then json displays in helpInformation', () => { + const program = new commander.Command(); + program + .option('--host ', 'select host', { host: 'example.com' }); + const helpInformation = program.helpInformation(); + expect(helpInformation).not.toContain('[object Object]'); + expect(helpInformation).toContain('default: {"host":"example.com"}'); +}); + +test('when default value is null then null displays in helpInformation', () => { + const program = new commander.Command(); + program + .option('--host ', 'select host', null); + const helpInformation = program.helpInformation(); + expect(helpInformation).toContain('default: null'); +}); + +test('when default value is null prototype then null displays in helpInformation', () => { + const program = new commander.Command(); + program + .option('--host ', 'select host', Object.create(null)); + const helpInformation = program.helpInformation(); + expect(helpInformation).toContain('default: {}'); +}); + +test('when default value is array of object with custom toString then custom string displays in helpInformation', () => { + const program = new commander.Command(); + program + .option('--host ', 'select host', [new URL('http://example.com/'), new URL('http://example.org/')]); + const helpInformation = program.helpInformation(); + expect(helpInformation).toContain('default: http://example.com/,http://example.org/'); +}); + +test('when default value is array of object without custom toString then json displays in helpInformation', () => { + const program = new commander.Command(); + program + .option('--host ', 'select host', [{ host: 'example.com' }, { host: 'example.org' }]); + const helpInformation = program.helpInformation(); + expect(helpInformation).not.toContain('[object Object]'); + expect(helpInformation).toContain('{"host":"example.com"},{"host":"example.org"}'); +}); + +test('when default value is array of object with mix of custom toString then custom string or json displays in helpInformation', () => { + const program = new commander.Command(); + program + .option('--host ', 'select host', [new URL('http://example.com/'), { host: 'example.com' }]); + const helpInformation = program.helpInformation(); + expect(helpInformation).not.toContain('[object Object]'); + expect(helpInformation).toContain('http://example.com/,{"host":"example.com"}'); +}); diff --git a/typings/commander-tests.ts b/typings/commander-tests.ts index 57392c667..823562252 100644 --- a/typings/commander-tests.ts +++ b/typings/commander-tests.ts @@ -104,6 +104,10 @@ function commaSeparatedList(value: string, dummyPrevious: string[]): string[] { return value.split(','); } +function parseObject(value: string): object { + return JSON.parse(value); +} + const optionThis5: commander.Command = program.option('-f, --float ', 'float argument', parseFloat); const optionThis6: commander.Command = program.option('-f, --float ', 'float argument', parseFloat, 3.2); const optionThis7: commander.Command = program.option('-i, --integer ', 'integer argument', myParseInt); @@ -111,6 +115,8 @@ const optionThis8: commander.Command = program.option('-i, --integer ', const optionThis9: commander.Command = program.option('-v, --verbose', 'verbosity that can be increased', increaseVerbosity, 0); const optionThis10: commander.Command = program.option('-c, --collect ', 'repeatable value', collect, []); const optionThis11: commander.Command = program.option('-l, --list ', 'comma separated list', commaSeparatedList); +const optionThis12: commander.Command = program.option('-o, --object ', 'object argument', parseObject, {}); +const optionThis13: commander.Command = program.option('-o, --object ', 'object argument', parseObject, null); // requiredOption, same tests as option const requiredOptionThis1: commander.Command = program.requiredOption('-a,--alpha'); @@ -125,6 +131,8 @@ const requiredOptionThis8: commander.Command = program.requiredOption('-i, --int const requiredOptionThis9: commander.Command = program.requiredOption('-v, --verbose', 'verbosity that can be increased', increaseVerbosity, 0); const requiredOptionThis10: commander.Command = program.requiredOption('-c, --collect ', 'repeatable value', collect, []); const requiredOptionThis11: commander.Command = program.requiredOption('-l, --list ', 'comma separated list', commaSeparatedList); +const requiredOptionThis12: commander.Command = program.requiredOption('-o, --object ', 'object argument', parseObject, {}); +const requiredOptionThis13: commander.Command = program.requiredOption('-o, --object ', 'object argument', parseObject, null); // storeOptionsAsProperties const storeOptionsAsPropertiesThis1: commander.Command = program.storeOptionsAsProperties();