Repository: yargs/yargs-parser
Pull request #251 (2020-03-02 05:35:42)

feat!: NaN can now be provided as a value for nargs, indicating "at least" one value is expected for array
Introduces special handling of NaN, which yargs will use rather than 1 to indicate that
an argument has a required value following the flag.

BREAKING CHANGE: the narg count is now enforced when parsing arrays.


---

This is in reaction to https://github.com/mochajs/mocha/pull/4196, https://github.com/yargs/yargs/issues/1098, and https://github.com/yargs/yargs/issues/1570. It will eliminate ambiguity in `requiresArg` vs., `nargs = `, by moving `requiresArg` to using the special `NaN` value for `nargs`.


Patch:
diff --git a/index.js b/index.js
index 542f681..a0367bd 100644
--- a/index.js
+++ b/index.js
@@ -361,7 +361,10 @@ function parse (args, opts) {
   // on the nargs option?
   function eatNargs (i, key, args) {
     let ii
-    const toEat = checkAllAliases(key, flags.nargs)
+    let toEat = checkAllAliases(key, flags.nargs)
+    // NaN has a special meaning for the array type, indicating that one or
+    // more values are expected.
+    toEat = isNaN(toEat) ? 1 : toEat
 
     if (toEat === 0) {
       setArg(key, defaultValue(key))
@@ -397,6 +400,8 @@ function parse (args, opts) {
   function eatArray (i, key, args) {
     let argsToSet = []
     let next = args[i + 1]
+    // If both array and nargs are configured, enforce the nargs count:
+    const nargsCount = checkAllAliases(key, flags.nargs)
 
     if (checkAllAliases(key, flags.bools) && !(/^(true|false)$/.test(next))) {
       argsToSet.push(true)
@@ -413,14 +418,16 @@ function parse (args, opts) {
         if (/^-/.test(next) && !negative.test(next) && !isUnknownOptionAsArg(next)) break
         i = ii
         argsToSet.push(processValue(key, next))
-        if (!configuration['greedy-arrays']) break
+        if (!configuration['greedy-arrays'] ||
+            (nargsCount && argsToSet.length >= nargsCount)) break
       }
     }
 
     // If both array and nargs are configured, create an error if less than
-    // nargs positionals were found:
-    const toEat = checkAllAliases(key, flags.nargs)
-    if (toEat && argsToSet.length < toEat) {
+    // nargs positionals were found. NaN has special meaning, indicating
+    // that at least one value is required (more are okay).
+    if ((nargsCount && argsToSet.length < nargsCount) ||
+        (isNaN(nargsCount) && argsToSet.length === 0)) {
       error = Error(__('Not enough arguments following: %s', key))
     }
 


Test patch:
diff --git a/test/yargs-parser.js b/test/yargs-parser.js
index 8160562..4a16cac 100644
--- a/test/yargs-parser.js
+++ b/test/yargs-parser.js
@@ -3565,28 +3565,65 @@ describe('yargs-parser', function () {
     parse.infinite.should.equal(false)
   })
 
-  // See: https://github.com/yargs/yargs/issues/1098
+  // See: https://github.com/yargs/yargs/issues/1098,
+  // https://github.com/yargs/yargs/issues/1570
   describe('array with nargs', () => {
     it('allows array and nargs to be configured in conjunction, enforcing the nargs value', () => {
-      var parse = parser(['-a', 'apple', 'banana'], {
+      var parse = parser.detailed(['-a', 'apple', 'banana'], {
         array: 'a',
         narg: {
           a: 1
         }
       })
-      parse.a.should.eql(['apple', 'banana'])
+      expect(parse.error).to.be.null // eslint-disable-line
+      parse.argv.a.should.eql(['apple'])
+      parse.argv._.should.eql(['banana'])
     })
 
-    it('returns an error if not enough positionals were provided for nargs', () => {
+    // see; https://github.com/yargs/yargs/issues/1098
+    it('allows special NaN count to be provided to narg, to indicate one or more array values', () => {
+      var parse = parser.detailed(['-a', 'apple', 'banana'], {
+        array: 'a',
+        narg: {
+          a: NaN
+        }
+      })
+      expect(parse.error).to.be.null // eslint-disable-line
+      parse.argv.a.should.eql(['apple', 'banana'])
+    })
+
+    it('throws error if at least one value not provided for NaN', () => {
       var parse = parser.detailed(['-a'], {
         array: 'a',
         narg: {
-          a: 1
+          a: NaN
+        }
+      })
+      parse.error.message.should.match(/Not enough arguments/)
+    })
+
+    it('returns an error if not enough positionals were provided for nargs', () => {
+      var parse = parser.detailed(['-a', '33'], {
+        array: 'a',
+        narg: {
+          a: 2
         }
       })
-      parse.argv.a.should.eql([])
+      parse.argv.a.should.eql([33])
       parse.error.message.should.equal('Not enough arguments following: a')
     })
+
+    it('does not raise error if no arguments are provided for boolean option', () => {
+      var parse = parser.detailed(['-a'], {
+        array: 'a',
+        boolean: 'a',
+        narg: {
+          a: NaN
+        }
+      })
+      expect(parse.error).to.be.null // eslint-disable-line
+      parse.argv.a.should.eql([true])
+    })
   })
 
   describe('greedy-arrays=false', () => {