Repository: yargs/yargs (js)

Issue:
Hyphenated option breaks strict validation
Version: 10.0.3

Take this sample code:
```ts
import * as yargs from 'yargs';

yargs.option('awesome-arg', {
  string: true
}).strict().parse();

console.log('Parsed');
```

Running the sample with various arguments:
```
--awesomes-arg         // Strict mode is expected to fail here
--awesome-args         // and here
--awesomes-args        // here too
--awesomes-args --test // Strict mode only fails on `test`
--awesome-arg          // Strict mode successful as expected
```
  

Pull request description:
Make strict() work with options containing hyphens
Fixes #1039 #1171 #1236 #1325

Patch:
diff --git a/docs/api.md b/docs/api.md
index 5912252..9b88291 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -137,10 +137,10 @@ var argv = require('yargs')
 Provide a synchronous function to coerce or transform the value(s) given on the
 command line for `key`.
 
-The coercion function should accept one argument, representing the parsed value
-from the command line, and should return a new value or throw an error. The
-returned value will be used as the value for `key` (or one of its aliases) in
-`argv`.
+The coercion function should accept one argument, representing the parsed value from
+the command line (an array if multiple values are parsed for the key), and should 
+return a new value or throw an error. The returned value will be used as the value for
+`key` (or one of its aliases) in `argv`.
 
 If the function throws, the error will be treated as a validation
 failure, delegating to either a custom [`.fail()`](#fail) handler or printing
diff --git a/lib/validation.js b/lib/validation.js
index 527a0d5..1a31e58 100644
--- a/lib/validation.js
+++ b/lib/validation.js
@@ -96,7 +96,7 @@ module.exports = function validation (yargs, usage, y18n) {
       if (specialKeys.indexOf(key) === -1 &&
         !positionalMap.hasOwnProperty(key) &&
         !yargs._getParseContext().hasOwnProperty(key) &&
-        !aliases.hasOwnProperty(key)
+        !self.isValidAndSomeAliasIsNotNew(key, aliases)
       ) {
         unknown.push(key)
       }
@@ -120,6 +120,21 @@ module.exports = function validation (yargs, usage, y18n) {
     }
   }
 
+  // check for a key that is not an alias, or for which every alias is new,
+  // implying that it was invented by the parser, e.g., during camelization
+  self.isValidAndSomeAliasIsNotNew = function isValidAndSomeAliasIsNotNew (key, aliases) {
+    if (!aliases.hasOwnProperty(key)) {
+      return false
+    }
+    const newAliases = yargs.parsed.newAliases
+    for (let a of [key, ...aliases[key]]) {
+      if (!newAliases.hasOwnProperty(a) || !newAliases[key]) {
+        return true
+      }
+    }
+    return false
+  }
+
   // validate arguments limited to enumerated choices
   self.limitedChoices = function limitedChoices (argv) {
     const options = yargs.getOptions()


Test patch:
diff --git a/test/usage.js b/test/usage.js
index f3c9725..75918ac 100644
--- a/test/usage.js
+++ b/test/usage.js
@@ -810,6 +810,75 @@ describe('usage tests', () => {
       r.should.have.property('exit').and.equal(true)
     })
 
+    describe('with hyphens in options', () => {
+      it('fails when an invalid argument is provided', (done) => {
+        return yargs('--foo-bar')
+          .strict()
+          .fail((msg) => {
+            return done()
+          })
+          .argv
+      })
+
+      it('accepts valid options', () => {
+        const r = checkUsage(() => {
+          const opts = {
+            '--foo-bar': { description: 'foo bar option' },
+            '--bar-baz': { description: 'bar baz option' }
+          }
+
+          return yargs('--foo-bar --bar-baz')
+            .options(opts)
+            .strict()
+            .parse()
+        })
+
+        r.result.should.have.property('foo-bar', true)
+        r.result.should.have.property('fooBar', true)
+        r.result.should.have.property('bar-baz', true)
+        r.result.should.have.property('barBaz', true)
+      })
+
+      it('works with aliases', () => {
+        const r = checkUsage(() => {
+          const opts = {
+            '--foo-bar': { description: 'foo bar option', alias: 'f' },
+            '--bar-baz': { description: 'bar baz option', alias: 'b' }
+          }
+
+          return yargs('--foo-bar -b')
+            .options(opts)
+            .strict()
+            .parse()
+        })
+
+        r.result.should.have.property('foo-bar', true)
+        r.result.should.have.property('fooBar', true)
+        r.result.should.have.property('f', true)
+        r.result.should.have.property('bar-baz', true)
+        r.result.should.have.property('barBaz', true)
+        r.result.should.have.property('b', true)
+      })
+
+      it('accepts mixed options with values', () => {
+        const r = checkUsage(() => {
+          const opts = {
+            '--foo-bar': { description: 'foo bar option', demand: true },
+            '--baz': { description: 'baz option', demand: true }
+          }
+
+          return yargs('--foo-bar 150 --baz')
+            .options(opts)
+            .strict()
+            .parse()
+        })
+
+        r.result.should.have.property('foo-bar', 150)
+        r.result.should.have.property('fooBar', 150)
+        r.result.should.have.property('baz', true)
+      })
+    })
+
     it('should fail given an option argument without a corresponding description', () => {
       const r = checkUsage(() => {
         const opts = {