Repository: yargs/yargs-parser
Pull request #185 (2019-06-25 13:40:28)

Fix: boolean[] -  eatArray() with boolean typed arrays and user defaults
### Description

```js 
var args = parse('--file --file', {
    array: ['file'],
    boolean: ['file'],
    configuration: {
      'duplicate-arguments-array': true,
      'flatten-duplicate-arrays': false
    }
})      // { _: [], file: [ [], [], [] ] }
```
The result is incorrect, it should be: **{ _: [], file: [ [true], [true] ] }**

### Description of Change

- `eatArray()` is called even for flags without arguments. This enables to set:
  - `boolean` with short syntax ('--bool --bool') which carries its implicit value `true`
  - default values given by `opts.default` for all type of options (except `boolean`)
- the fallback default value for all type of options is an empty array `[]`. 
- `eatArray()`: remove the `multipleArrayFlag` logic. The "duplicate array" pattern is already handled within `setKey()` depending on the `flatten-duplicate-arrays` configuration option. `setKey()` remains unchanged.


bcoe: @juergba this is looking like a reasonable start to me, looks like there's an outstanding `TODO`?
bcoe: @juergba :wave: just following up, would love to see this over the finish line.

Patch:
diff --git a/index.js b/index.js
index 1b82103..79d433c 100644
--- a/index.js
+++ b/index.js
@@ -156,7 +156,7 @@ function parse (args, opts) {
         args.splice(i + 1, 0, m[2])
         i = eatNargs(i, m[1], args)
       // arrays format = '--f=a b c'
-      } else if (checkAllAliases(m[1], flags.arrays) && args.length > i + 1) {
+      } else if (checkAllAliases(m[1], flags.arrays)) {
         args.splice(i + 1, 0, m[2])
         i = eatArray(i, m[1], args)
       } else {
@@ -164,7 +164,7 @@ function parse (args, opts) {
       }
     } else if (arg.match(negatedBoolean) && configuration['boolean-negation']) {
       key = arg.match(negatedBoolean)[1]
-      setArg(key, false)
+      setArg(key, checkAllAliases(key, flags.arrays) ? [false] : false)
 
     // -- seperated by space.
     } else if (arg.match(/^--.+/) || (
@@ -177,7 +177,7 @@ function parse (args, opts) {
       if (checkAllAliases(key, flags.nargs) !== false) {
         i = eatNargs(i, key, args)
       // array format = '--foo a b c'
-      } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) {
+      } else if (checkAllAliases(key, flags.arrays)) {
         i = eatArray(i, key, args)
       } else {
         next = args[i + 1]
@@ -230,7 +230,7 @@ function parse (args, opts) {
             args.splice(i + 1, 0, value)
             i = eatNargs(i, key, args)
           // array format = '-f=a b c'
-          } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) {
+          } else if (checkAllAliases(key, flags.arrays)) {
             args.splice(i + 1, 0, value)
             i = eatArray(i, key, args)
           } else {
@@ -271,7 +271,7 @@ function parse (args, opts) {
         if (checkAllAliases(key, flags.nargs) !== false) {
           i = eatNargs(i, key, args)
         // array format = '-f a b c'
-        } else if (checkAllAliases(key, flags.arrays) && args.length > i + 1) {
+        } else if (checkAllAliases(key, flags.arrays)) {
           i = eatArray(i, key, args)
         } else {
           next = args[i + 1]
@@ -376,30 +376,27 @@ function parse (args, opts) {
   // following it... YUM!
   // e.g., --foo apple banana cat becomes ["apple", "banana", "cat"]
   function eatArray (i, key, args) {
-    var start = i + 1
-    var argsToSet = []
-    var multipleArrayFlag = i > 0
-    for (var ii = i + 1; ii < args.length; ii++) {
-      if (/^-/.test(args[ii]) && !negative.test(args[ii])) {
-        if (ii === start) {
-          setArg(key, defaultForType('array'))
-        }
-        multipleArrayFlag = true
-        break
+    let argsToSet = []
+    let next = args[i + 1]
+
+    if (checkAllAliases(key, flags.bools) && !(/^(true|false)$/.test(next))) {
+      argsToSet.push(true)
+    } else if (isUndefined(next) || (/^-/.test(next) && !negative.test(next))) {
+      // for keys without value ==> argsToSet remains an empty []
+      // set user default value, if available
+      if (defaults.hasOwnProperty(key)) {
+        argsToSet.push(defaults[key])
       }
-      i = ii
-      argsToSet.push(args[ii])
-    }
-    if (multipleArrayFlag) {
-      setArg(key, argsToSet.map(function (arg) {
-        return processValue(key, arg)
-      }))
     } else {
-      argsToSet.forEach(function (arg) {
-        setArg(key, arg)
-      })
+      for (var ii = i + 1; ii < args.length; ii++) {
+        next = args[ii]
+        if (/^-/.test(next) && !negative.test(next)) break
+        i = ii
+        argsToSet.push(processValue(key, next))
+      }
     }
 
+    setArg(key, argsToSet)
     return i
   }
 
@@ -791,6 +788,7 @@ function parse (args, opts) {
 
     if (checkAllAliases(key, flags.strings)) type = 'string'
     else if (checkAllAliases(key, flags.numbers)) type = 'number'
+    else if (checkAllAliases(key, flags.bools)) type = 'boolean'
     else if (checkAllAliases(key, flags.arrays)) type = 'array'
 
     return type


Test patch:
diff --git a/test/yargs-parser.js b/test/yargs-parser.js
index d5532ac..65d90e4 100644
--- a/test/yargs-parser.js
+++ b/test/yargs-parser.js
@@ -1492,6 +1492,14 @@ describe('yargs-parser', function () {
       result.should.have.property('b').and.deep.equal([])
     })
 
+    it('should place default of argument in array, when default provided', function () {
+      var result = parser(['-b'], {
+        array: 'b',
+        default: { 'b': 33 }
+      })
+      result.should.have.property('b').and.deep.equal([33])
+    })
+
     it('should place value of argument in array, when one argument provided', function () {
       var result = parser(['-b', '33'], {
         array: ['b']
@@ -1618,6 +1626,22 @@ describe('yargs-parser', function () {
       result.should.have.property('x').that.is.an('array').and.to.deep.equal([true, false])
     })
 
+    it('should respect type `boolean` without value for arrays', function () {
+      var result = parser(['-x', '-x'], {
+        array: [{ key: 'x', boolean: true }],
+        configuration: { 'flatten-duplicate-arrays': false }
+      })
+      result.x.should.deep.equal([[true], [true]])
+    })
+
+    it('should respect `boolean negation` for arrays', function () {
+      var result = parser(['--no-bool', '--no-bool'], {
+        array: [{ key: 'bool', boolean: true }],
+        configuration: { 'flatten-duplicate-arrays': false }
+      })
+      result.bool.should.deep.equal([[false], [false]])
+    })
+
     it('should respect the type `number` option for arrays', function () {
       var result = parser(['-x=5', '2'], {
         array: [{ key: 'x', number: true }]