Skip to content

Commit

Permalink
feat: adds strictOptions() (#1738)
Browse files Browse the repository at this point in the history
  • Loading branch information
bcoe committed Sep 9, 2020
1 parent c7debe8 commit b215fba
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 22 deletions.
7 changes: 7 additions & 0 deletions docs/api.md
Expand Up @@ -1518,6 +1518,13 @@ Similar to `.strict()`, except that it only applies to unrecognized commands. A
user can still provide arbitrary options, but unknown positional commands
will raise an error.

.strictOptions([enabled=true])
---------

Similar to `.strict()`, except that it only applies to unrecognized options. A
user can still provide arbitrary positional commands, but unknown options
will raise an error.

<a name="string"></a>.string(key)
------------

Expand Down
7 changes: 4 additions & 3 deletions lib/validation.ts
Expand Up @@ -114,7 +114,7 @@ export function validation (yargs: YargsInstance, usage: UsageInstance, y18n: Y1
}

// check for unknown arguments (strict-mode).
self.unknownArguments = function unknownArguments (argv, aliases, positionalMap, isDefaultCommand) {
self.unknownArguments = function unknownArguments (argv, aliases, positionalMap, isDefaultCommand, checkPositionals = true) {
const commandKeys = yargs.getCommandInstance().getCommands()
const unknown: string[] = []
const currentContext = yargs.getContext()
Expand All @@ -129,7 +129,7 @@ export function validation (yargs: YargsInstance, usage: UsageInstance, y18n: Y1
}
})

if ((currentContext.commands.length > 0) || (commandKeys.length > 0) || isDefaultCommand) {
if (checkPositionals && ((currentContext.commands.length > 0) || (commandKeys.length > 0) || isDefaultCommand)) {
argv._.slice(currentContext.commands.length).forEach((key) => {
if (commandKeys.indexOf('' + key) === -1) {
unknown.push('' + key)
Expand Down Expand Up @@ -428,7 +428,8 @@ export interface ValidationInstance {
argv: Arguments,
aliases: DetailedArguments['aliases'],
positionalMap: Dictionary,
isDefaultCommand: boolean
isDefaultCommand: boolean,
checkPositionals?: boolean,
): void
unknownCommands(argv: Arguments): boolean
}
Expand Down
15 changes: 15 additions & 0 deletions lib/yargs-factory.ts
Expand Up @@ -167,6 +167,7 @@ function Yargs (processArgs: string | string[] = [], cwd = shim.process.cwd(), p
groups,
strict,
strictCommands,
strictOptions,
completionCommand,
output,
exitError,
Expand Down Expand Up @@ -195,6 +196,7 @@ function Yargs (processArgs: string | string[] = [], cwd = shim.process.cwd(), p
parsed: self.parsed,
strict,
strictCommands,
strictOptions,
completionCommand,
parseFn,
parseContext,
Expand Down Expand Up @@ -950,6 +952,14 @@ function Yargs (processArgs: string | string[] = [], cwd = shim.process.cwd(), p
}
self.getStrictCommands = () => strictCommands

let strictOptions = false
self.strictOptions = function (enabled) {
argsert('[boolean]', [enabled], arguments.length)
strictOptions = enabled !== false
return self
}
self.getStrictOptions = () => strictOptions

let parserConfig: Configuration = {}
self.parserConfiguration = function parserConfiguration (config) {
argsert('<object>', [config], arguments.length)
Expand Down Expand Up @@ -1379,6 +1389,8 @@ function Yargs (processArgs: string | string[] = [], cwd = shim.process.cwd(), p
}
if (strict && !failedStrictCommands) {
validation.unknownArguments(argv, aliases, positionalMap, isDefaultCommand)
} else if (strictOptions) {
validation.unknownArguments(argv, aliases, {}, false, false)
}
validation.customChecks(argv, aliases)
validation.limitedChoices(argv)
Expand Down Expand Up @@ -1524,6 +1536,7 @@ export interface YargsInstance {
getParserConfiguration (): Configuration
getStrict (): boolean
getStrictCommands (): boolean
getStrictOptions (): boolean
getUsageInstance (): UsageInstance
getValidationInstance (): ValidationInstance
global (keys: string | string[], global?: boolean): YargsInstance
Expand Down Expand Up @@ -1578,6 +1591,7 @@ export interface YargsInstance {
skipValidation (keys: string | string[]): YargsInstance
strict (enable?: boolean): YargsInstance
strictCommands (enable?: boolean): YargsInstance
strictOptions (enable?: boolean): YargsInstance
string (key: string | string []): YargsInstance
terminalWidth (): number | null
updateStrings (obj: Dictionary<string>): YargsInstance
Expand Down Expand Up @@ -1711,6 +1725,7 @@ interface FrozenYargsInstance {
groups: Dictionary<string[]>
strict: boolean
strictCommands: boolean
strictOptions: boolean
completionCommand: string | null
output: string
exitError: YError | string | undefined | null
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -28,7 +28,7 @@
"index.cjs",
"helpers.mjs",
"index.mjs",
"yargs.mjs",
"yargs.cjs",
"build",
"locales",
"LICENSE",
Expand Down
66 changes: 48 additions & 18 deletions test/validation.cjs
Expand Up @@ -999,24 +999,6 @@ describe('validation tests', () => {
.parse()
})

// TODO(bcoe): consider implementing this behvaior in the next major version of yargs:
//
// // for the special case of yargs.demandCommand() and yargs.demandCommand(1), if
// // yargs has been configured with commands, we automatically enable strictCommands.
// if (commandKeys.length && demandedCommands._ && demandedCommands._.min === 1 && demandedCommands._.max === Infinity) {
// yargs.strictCommands()
// }
// it('enables strict commands if commands used in conjunction with demandCommand', (done) => {
// yargs('blerg -a 10')
// .demandCommand()
// .command('foo', 'foo command')
// .fail((msg) => {
// msg.should.equal('Unknown command: blerg')
// return done()
// })
// .parse()
// })

it('does not apply implicit strictCommands to inner commands', () => {
const parse = yargs('foo blarg --cool beans')
.demandCommand()
Expand All @@ -1040,4 +1022,52 @@ describe('validation tests', () => {
.parse()
})
})

describe('strictOptions', () => {
it('succeeds if option is known and command is unknown', (done) => {
yargs()
.command('foo', 'foo command')
.option('a', {
describe: 'a is for option'
})
.strictOptions()
.parse('bar -a 10', (err, argv) => {
expect(err).to.equal(null)
argv.a.should.equal(10)
return done()
})
})

it('fails if option is unknown', (done) => {
yargs()
.strictOptions()
.parse('bar -a 10', (err, argv) => {
expect(err).to.match(/Unknown argument: a/)
argv.a.should.equal(10)
return done()
})
})

it('applies strict options when commands are invoked', () => {
yargs()
.strictOptions()
.parse('foo --cool --awesome', (err) => {
expect(err).to.match(/Unknown arguments: cool, awesome/)
})
})

it('allows strict options to be turned off', () => {
const y = yargs()
.strictOptions()
.command('foo', 'foo command', (yargs) => {
yargs.strictOptions(false)
})
y.parse('foo --cool --awesome', (err) => {
expect(err).to.equal(null)
})
y.parse('--cool --awesome', (err) => {
expect(err).to.match(/Unknown arguments: cool, awesome/)
})
})
})
})

0 comments on commit b215fba

Please sign in to comment.