diff --git a/docs/api.md b/docs/api.md index dc588100c..4943a33a3 100644 --- a/docs/api.md +++ b/docs/api.md @@ -198,11 +198,14 @@ all other modifications, such as [`.normalize()`](#normalize). _Examples:_ ```js -var argv = require('yargs/yargs')(process.argv.slice(2)) - .coerce('file', function (arg) { - return await require('fs').promises.readFile(arg, 'utf8') +import { readFile } from 'node:fs/promises'; +import yargs from 'yargs'; +const argv = await yargs(process.argv.slice(2)) + .coerce('file', async (arg) => { + const content = await readFile(arg, 'utf8'); + return JSON.parse(content); }) - .argv + .parseAsync(); ``` Optionally `.coerce()` can take an object that maps several keys to their diff --git a/lib/yargs-factory.ts b/lib/yargs-factory.ts index f3c039d7a..1d58fdf33 100644 --- a/lib/yargs-factory.ts +++ b/lib/yargs-factory.ts @@ -380,20 +380,26 @@ export class YargsInstance { if (!value) { throw new YError('coerce callback must be provided'); } + + // Handled multiple above, down to one key. + const coerceKey = keys; // This noop tells yargs-parser about the existence of the option - // represented by "keys", so that it can apply camel case expansion + // represented by "coerceKey", so that it can apply camel case expansion // if needed: - this.#options.key[keys] = true; + this.#options.key[coerceKey] = true; this.#globalMiddleware.addCoerceMiddleware( ( argv: Arguments, yargs: YargsInstance ): Partial | Promise> => { - let aliases: Dictionary; + // Narrow down the possible keys to the ones present in argv. + const coerceKeyAliases = yargs.getAliases()[coerceKey] ?? []; + const argvKeys = [coerceKey, ...coerceKeyAliases].filter(key => + Object.prototype.hasOwnProperty.call(argv, key) + ); - // Skip coerce logic if related arg was not provided - const shouldCoerce = Object.prototype.hasOwnProperty.call(argv, keys); - if (!shouldCoerce) { + // Skip coerce if nothing to coerce. + if (argvKeys.length === 0) { return argv; } @@ -401,19 +407,12 @@ export class YargsInstance { Partial | Promise> | any >( () => { - aliases = yargs.getAliases(); - return value(argv[keys]); + return value(argv[argvKeys[0]]); }, (result: any): Partial => { - argv[keys] = result; - const stripAliased = yargs - .getInternalMethods() - .getParserConfiguration()['strip-aliased']; - if (aliases[keys] && stripAliased !== true) { - for (const alias of aliases[keys]) { - argv[alias] = result; - } - } + argvKeys.forEach(key => { + argv[key] = result; + }); return argv; }, (err: Error): Partial | Promise> => { @@ -421,7 +420,7 @@ export class YargsInstance { } ); }, - keys + coerceKey ); return this; } diff --git a/package.json b/package.json index 389cc6b06..a7a4db60a 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "c8": "^7.7.0", "chai": "^4.2.0", "chalk": "^4.0.0", + "concurrently": "^7.6.0", "coveralls": "^3.0.9", "cpr": "^3.0.1", "cross-env": "^7.0.2", @@ -100,7 +101,10 @@ "postbuild:cjs": "rimraf ./build/index.cjs.d.ts", "check": "gts lint && npm run check:js", "check:js": "eslint . --ext cjs --ext mjs --ext js", - "clean": "gts clean" + "clean": "gts clean", + "watch": "rimraf build && tsc && concurrently npm:watch:tsc npm:watch:cjs", + "watch:cjs": "rollup -w -c rollup.config.cjs", + "watch:tsc": "tsc --watch" }, "repository": { "type": "git", diff --git a/test/yargs.cjs b/test/yargs.cjs index 42b4b83b8..2011a8288 100644 --- a/test/yargs.cjs +++ b/test/yargs.cjs @@ -2238,6 +2238,76 @@ describe('yargs dsl tests', () => { .getHelp(); help.should.match(/option2 description/); }); + + it('argv includes coerced aliases', () => { + const argv = yargs('--foo bar') + .option('foo', { + coerce: s => s.toUpperCase(), + alias: 'f', + }) + .parse(); + argv['foo'].should.equal('BAR'); + argv['f'].should.equal('BAR'); + }); + + it('argv includes coerced camelCase', () => { + const argv = yargs('--foo-foo bar') + .option('foo-foo', { + coerce: s => s.toUpperCase(), + }) + .parse(); + argv['foo-foo'].should.equal('BAR'); + argv['fooFoo'].should.equal('BAR'); + }); + + it('coerce still works when key used for coerce is not explicitly present in argv', () => { + const argv = yargs('--foo-foo bar') + .option('foo-foo') + .coerce('foo-foo', s => s.toUpperCase()) + .parserConfiguration({'strip-dashed': true}) + .parse(); + expect(argv['foo-foo']).to.equal(undefined); + argv['fooFoo'].should.equal('BAR'); + }); + + it('argv does not include stripped aliases', () => { + const argv = yargs('-f bar') + .option('foo-foo', { + coerce: s => s.toUpperCase(), + alias: 'f', + }) + .parserConfiguration({'strip-aliased': true}) + .parse(); + argv['foo-foo'].should.equal('BAR'); + argv['fooFoo'].should.equal('BAR'); + expect(argv['f']).to.equal(undefined); + }); + + it('argv does not include stripped dashes', () => { + const argv = yargs('-f bar') + .option('foo-foo', { + coerce: s => s.toUpperCase(), + alias: 'f', + }) + .parserConfiguration({'strip-dashed': true}) + .parse(); + expect(argv['foo-foo']).to.equal(undefined); + argv['fooFoo'].should.equal('BAR'); + argv['f'].should.equal('BAR'); + }); + + it('argv does not include disabled camel-case-expansion', () => { + const argv = yargs('-f bar') + .option('foo-foo', { + coerce: s => s.toUpperCase(), + alias: 'f', + }) + .parserConfiguration({'camel-case-expansion': false}) + .parse(); + argv['foo-foo'].should.equal('BAR'); + expect(argv['fooFoo']).to.equal(undefined); + argv['f'].should.equal('BAR'); + }); }); describe('stop parsing', () => {