Skip to content

Commit 026b151

Browse files
mleguenBenjamin E. Coe
authored andcommitted
fix!: calling parse multiple times now appropriately maintains state (#1137) (#1369)
BREAKING CHANGE: previously to this fix methods like `yargs.getOptions()` contained the state of the last command to execute.
1 parent 6e5b76b commit 026b151

File tree

5 files changed

+68
-38
lines changed

5 files changed

+68
-38
lines changed

lib/command.js

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -186,24 +186,17 @@ module.exports = function command (yargs, usage, validation, globalMiddleware) {
186186
// a function can be provided, which builds
187187
// up a yargs chain and possibly returns it.
188188
innerYargs = commandHandler.builder(yargs.reset(parsed.aliases))
189-
// if the builder function did not yet parse argv with reset yargs
190-
// and did not explicitly set a usage() string, then apply the
191-
// original command string as usage() for consistent behavior with
192-
// options object below.
193-
if (yargs.parsed === false) {
194-
if (shouldUpdateUsage(yargs)) {
195-
yargs.getUsageInstance().usage(
196-
usageFromParentCommandsCommandHandler(parentCommands, commandHandler),
197-
commandHandler.description
198-
)
199-
}
200-
innerArgv = innerYargs ? innerYargs._parseArgs(null, null, true, commandIndex) : yargs._parseArgs(null, null, true, commandIndex)
201-
} else {
202-
innerArgv = yargs.parsed.argv
189+
if (!innerYargs || (typeof innerYargs._parseArgs !== 'function')) {
190+
innerYargs = yargs
203191
}
204-
205-
if (innerYargs && yargs.parsed === false) aliases = innerYargs.parsed.aliases
206-
else aliases = yargs.parsed.aliases
192+
if (shouldUpdateUsage(innerYargs)) {
193+
innerYargs.getUsageInstance().usage(
194+
usageFromParentCommandsCommandHandler(parentCommands, commandHandler),
195+
commandHandler.description
196+
)
197+
}
198+
innerArgv = innerYargs._parseArgs(null, null, true, commandIndex)
199+
aliases = innerYargs.parsed.aliases
207200
} else if (typeof commandHandler.builder === 'object') {
208201
// as a short hand, an object can instead be provided, specifying
209202
// the options that a command takes.
@@ -419,18 +412,19 @@ module.exports = function command (yargs, usage, validation, globalMiddleware) {
419412
// the state of commands such that
420413
// we can apply .parse() multiple times
421414
// with the same yargs instance.
422-
let frozen
415+
let frozens = []
423416
self.freeze = () => {
424-
frozen = {}
417+
let frozen = {}
418+
frozens.push(frozen)
425419
frozen.handlers = handlers
426420
frozen.aliasMap = aliasMap
427421
frozen.defaultCommand = defaultCommand
428422
}
429423
self.unfreeze = () => {
424+
let frozen = frozens.pop()
430425
handlers = frozen.handlers
431426
aliasMap = frozen.aliasMap
432427
defaultCommand = frozen.defaultCommand
433-
frozen = undefined
434428
}
435429

436430
return self

lib/usage.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -512,9 +512,10 @@ module.exports = function usage (yargs, y18n) {
512512
return self
513513
}
514514

515-
let frozen
515+
let frozens = []
516516
self.freeze = function freeze () {
517-
frozen = {}
517+
let frozen = {}
518+
frozens.push(frozen)
518519
frozen.failMessage = failMessage
519520
frozen.failureOutput = failureOutput
520521
frozen.usages = usages
@@ -525,6 +526,7 @@ module.exports = function usage (yargs, y18n) {
525526
frozen.descriptions = descriptions
526527
}
527528
self.unfreeze = function unfreeze () {
529+
let frozen = frozens.pop()
528530
failMessage = frozen.failMessage
529531
failureOutput = frozen.failureOutput
530532
usages = frozen.usages
@@ -533,7 +535,6 @@ module.exports = function usage (yargs, y18n) {
533535
examples = frozen.examples
534536
commands = frozen.commands
535537
descriptions = frozen.descriptions
536-
frozen = undefined
537538
}
538539

539540
return self

lib/validation.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -323,18 +323,19 @@ module.exports = function validation (yargs, usage, y18n) {
323323
return self
324324
}
325325

326-
let frozen
326+
let frozens = []
327327
self.freeze = function freeze () {
328-
frozen = {}
328+
let frozen = {}
329+
frozens.push(frozen)
329330
frozen.implied = implied
330331
frozen.checks = checks
331332
frozen.conflicting = conflicting
332333
}
333334
self.unfreeze = function unfreeze () {
335+
let frozen = frozens.pop()
334336
implied = frozen.implied
335337
checks = frozen.checks
336338
conflicting = frozen.conflicting
337-
frozen = undefined
338339
}
339340

340341
return self

test/yargs.js

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,29 @@ describe('yargs dsl tests', () => {
835835

836836
r.logs[0].should.match(/Commands:[\s\S]*blerg command/)
837837
})
838+
839+
it('can be called multiple times with the same behavior', () => {
840+
let counter = { foobar: 0 }
841+
yargs(['test', 'foobar'])
842+
.command(
843+
'test <name>',
844+
'increases counter',
845+
yargs => yargs.positional('name', {
846+
aliases: 'n',
847+
describe: 'a name',
848+
choices: ['foobar'],
849+
type: 'string'
850+
}),
851+
argv => { counter[argv.name]++ }
852+
)
853+
.fail((msg) => {
854+
expect.fail(undefined, undefined, msg)
855+
})
856+
yargs.parse()
857+
yargs.parse()
858+
yargs.parse()
859+
expect(counter.foobar).to.equal(3)
860+
})
838861
})
839862

840863
describe('parsed', () => {
@@ -2060,17 +2083,23 @@ describe('yargs dsl tests', () => {
20602083
})
20612084

20622085
it('allows a defaultDescription to be set', () => {
2063-
yargs('cmd')
2086+
const r = checkOutput(() => yargs('cmd --help').wrap(null)
20642087
.command('cmd [heroes...]', 'a command', (yargs) => {
20652088
yargs.positional('heroes', {
20662089
default: ['batman', 'Iron Man'],
20672090
defaultDescription: 'batman and Iron Man'
20682091
})
20692092
}).parse()
2070-
2071-
yargs.getOptions().defaultDescription.should.deep.equal({
2072-
heroes: 'batman and Iron Man'
2073-
})
2093+
)
2094+
r.logs.join('\n').split(/\n+/).should.deep.equal([
2095+
'usage cmd [heroes...]',
2096+
'a command',
2097+
'Positionals:',
2098+
' heroes [array] [default: batman and Iron Man]',
2099+
'Options:',
2100+
' --help Show help [boolean]',
2101+
' --version Show version number [boolean]'
2102+
])
20742103
})
20752104

20762105
it('allows an implied argument to be specified', (done) => {

yargs.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,10 @@ function Yargs (processArgs, cwd, parentRequire) {
144144
self.resetOptions()
145145

146146
// temporary hack: allow "freezing" of reset-able state for parse(msg, cb)
147-
let frozen
147+
let frozens = []
148148
function freeze () {
149-
frozen = {}
149+
let frozen = {}
150+
frozens.push(frozen)
150151
frozen.options = options
151152
frozen.configObjects = options.configObjects.slice(0)
152153
frozen.exitProcess = exitProcess
@@ -160,8 +161,11 @@ function Yargs (processArgs, cwd, parentRequire) {
160161
frozen.exitError = exitError
161162
frozen.hasOutput = hasOutput
162163
frozen.parsed = self.parsed
164+
frozen.parseFn = parseFn
165+
frozen.parseContext = parseContext
163166
}
164167
function unfreeze () {
168+
let frozen = frozens.pop()
165169
options = frozen.options
166170
options.configObjects = frozen.configObjects
167171
exitProcess = frozen.exitProcess
@@ -175,9 +179,8 @@ function Yargs (processArgs, cwd, parentRequire) {
175179
command.unfreeze()
176180
strict = frozen.strict
177181
completionCommand = frozen.completionCommand
178-
parseFn = null
179-
parseContext = null
180-
frozen = undefined
182+
parseFn = frozen.parseFn
183+
parseContext = frozen.parseContext
181184
}
182185

183186
self.boolean = function (keys) {
@@ -538,8 +541,11 @@ function Yargs (processArgs, cwd, parentRequire) {
538541
let parseContext = null
539542
self.parse = function parse (args, shortCircuit, _parseFn) {
540543
argsert('[string|array] [function|boolean|object] [function]', [args, shortCircuit, _parseFn], arguments.length)
544+
freeze()
541545
if (typeof args === 'undefined') {
542-
return self._parseArgs(processArgs)
546+
const parsed = self._parseArgs(processArgs)
547+
unfreeze()
548+
return parsed
543549
}
544550

545551
// a context object can optionally be provided, this allows
@@ -560,7 +566,6 @@ function Yargs (processArgs, cwd, parentRequire) {
560566
// skipping validation, etc.
561567
if (!shortCircuit) processArgs = args
562568

563-
freeze()
564569
if (parseFn) exitProcess = false
565570

566571
const parsed = self._parseArgs(args, shortCircuit)

0 commit comments

Comments
 (0)