From 200552285c42a8dcecb19118be505ae8e8e7ff9d Mon Sep 17 00:00:00 2001 From: Wee Bit Date: Sat, 5 Aug 2023 11:32:16 +0300 Subject: [PATCH 1/3] Introduce _getCommandAndAncestors() Replaces getCommandAndParents(): - turned into an instance method - used "ancestors" instead of "parents" because it is more precise (cherry picked from commit aa280afbf8b0fde08566ebd9c20cc85df2a62c6a) --- lib/command.js | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/lib/command.js b/lib/command.js index 590a271dd..dce158bcc 100644 --- a/lib/command.js +++ b/lib/command.js @@ -109,6 +109,19 @@ class Command extends EventEmitter { return this; } + /** + * @returns {Command[]} + * @api private + */ + + _getCommandAndAncestors() { + const result = []; + for (let command = this; command; command = command.parent) { + result.push(command); + } + return result; + } + /** * Define a command. * @@ -825,7 +838,7 @@ Expecting one of '${allowedValues.join("', '")}'`); getOptionValueSourceWithGlobals(key) { // global overwrites local, like optsWithGlobals let source; - getCommandAndParents(this).forEach((cmd) => { + this._getCommandAndAncestors().forEach((cmd) => { if (cmd.getOptionValueSource(key) !== undefined) { source = cmd.getOptionValueSource(key); } @@ -1208,7 +1221,7 @@ Expecting one of '${allowedValues.join("', '")}'`); _chainOrCallHooks(promise, event) { let result = promise; const hooks = []; - getCommandAndParents(this) + this._getCommandAndAncestors() .reverse() .filter(cmd => cmd._lifeCycleHooks[event] !== undefined) .forEach(hookedCommand => { @@ -1577,7 +1590,7 @@ Expecting one of '${allowedValues.join("', '")}'`); */ optsWithGlobals() { // globals overwrite locals - return getCommandAndParents(this).reduce( + return this._getCommandAndAncestors().reduce( (combinedOptions, cmd) => Object.assign(combinedOptions, cmd.opts()), {} ); @@ -2022,7 +2035,7 @@ Expecting one of '${allowedValues.join("', '")}'`); } const context = this._getHelpContext(contextOptions); - getCommandAndParents(this).reverse().forEach(command => command.emit('beforeAllHelp', context)); + this._getCommandAndAncestors().reverse().forEach(command => command.emit('beforeAllHelp', context)); this.emit('beforeHelp', context); let helpInformation = this.helpInformation(context); @@ -2036,7 +2049,7 @@ Expecting one of '${allowedValues.join("', '")}'`); this.emit(this._helpLongFlag); // deprecated this.emit('afterHelp', context); - getCommandAndParents(this).forEach(command => command.emit('afterAllHelp', context)); + this._getCommandAndAncestors().forEach(command => command.emit('afterAllHelp', context)); } /** @@ -2179,18 +2192,4 @@ function incrementNodeInspectorPort(args) { }); } -/** - * @param {Command} startCommand - * @returns {Command[]} - * @api private - */ - -function getCommandAndParents(startCommand) { - const result = []; - for (let command = startCommand; command; command = command.parent) { - result.push(command); - } - return result; -} - exports.Command = Command; From da5a4992f7948ea05407b9fa6bb99062bb5b2659 Mon Sep 17 00:00:00 2001 From: Wee Bit Date: Sat, 5 Aug 2023 11:54:44 +0300 Subject: [PATCH 2/3] Use _getCommandAndAncestors() consistently (cherry picked from commit 777a4528a6160171740fc795b517c7264d736d26) --- lib/command.js | 15 +++++++-------- lib/help.js | 16 ++++++++-------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/command.js b/lib/command.js index dce158bcc..b1c5622dc 100644 --- a/lib/command.js +++ b/lib/command.js @@ -1378,13 +1378,13 @@ Expecting one of '${allowedValues.join("', '")}'`); _checkForMissingMandatoryOptions() { // Walk up hierarchy so can call in subcommand after checking for displaying help. - for (let cmd = this; cmd; cmd = cmd.parent) { + this._getCommandAndAncestors().forEach((cmd) => { cmd.options.forEach((anOption) => { if (anOption.mandatory && (cmd.getOptionValue(anOption.attributeName()) === undefined)) { cmd.missingMandatoryOptionValue(anOption); } }); - } + }); } /** @@ -1425,9 +1425,9 @@ Expecting one of '${allowedValues.join("', '")}'`); */ _checkForConflictingOptions() { // Walk up hierarchy so can call in subcommand after checking for displaying help. - for (let cmd = this; cmd; cmd = cmd.parent) { + this._getCommandAndAncestors().forEach((cmd) => { cmd._checkForConflictingLocalOptions(); - } + }); } /** @@ -1756,14 +1756,13 @@ Expecting one of '${allowedValues.join("', '")}'`); if (flag.startsWith('--') && this._showSuggestionAfterError) { // Looping to pick up the global options too let candidateFlags = []; - let command = this; - do { + for (const command of this._getCommandAndAncestors()) { const moreFlags = command.createHelp().visibleOptions(command) .filter(option => option.long) .map(option => option.long); candidateFlags = candidateFlags.concat(moreFlags); - command = command.parent; - } while (command && !command._enablePositionalOptions); + if (command._enablePositionalOptions) break; + } suggestion = suggestSimilar(flag, candidateFlags); } diff --git a/lib/help.js b/lib/help.js index 14e0fb9f3..7f1c2c354 100644 --- a/lib/help.js +++ b/lib/help.js @@ -101,10 +101,10 @@ class Help { if (!this.showGlobalOptions) return []; const globalOptions = []; - for (let parentCmd = cmd.parent; parentCmd; parentCmd = parentCmd.parent) { - const visibleOptions = parentCmd.options.filter((option) => !option.hidden); + cmd._getCommandAndAncestors().slice(1).forEach((ancestorCmd) => { + const visibleOptions = ancestorCmd.options.filter((option) => !option.hidden); globalOptions.push(...visibleOptions); - } + }); if (this.sortOptions) { globalOptions.sort(this.compareOptions); } @@ -240,11 +240,11 @@ class Help { if (cmd._aliases[0]) { cmdName = cmdName + '|' + cmd._aliases[0]; } - let parentCmdNames = ''; - for (let parentCmd = cmd.parent; parentCmd; parentCmd = parentCmd.parent) { - parentCmdNames = parentCmd.name() + ' ' + parentCmdNames; - } - return parentCmdNames + cmdName + ' ' + cmd.usage(); + let ancestorCmdNames = ''; + cmd._getCommandAndAncestors().slice(1).forEach((ancestorCmd) => { + ancestorCmdNames = ancestorCmd.name() + ' ' + ancestorCmdNames; + }); + return ancestorCmdNames + cmdName + ' ' + cmd.usage(); } /** From 8b11122d3965c42af19988933da01b3c93a8ec4c Mon Sep 17 00:00:00 2001 From: Wee Bit Date: Thu, 10 Aug 2023 03:22:55 +0300 Subject: [PATCH 3/3] Use _getCommandAndAncestors() less aggressively Only call when all elements are to be iterated. Resort to manual iteration via .parent in other cases. --- lib/command.js | 7 ++++--- lib/help.js | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/command.js b/lib/command.js index b1c5622dc..d0868fd7e 100644 --- a/lib/command.js +++ b/lib/command.js @@ -1756,13 +1756,14 @@ Expecting one of '${allowedValues.join("', '")}'`); if (flag.startsWith('--') && this._showSuggestionAfterError) { // Looping to pick up the global options too let candidateFlags = []; - for (const command of this._getCommandAndAncestors()) { + let command = this; + do { const moreFlags = command.createHelp().visibleOptions(command) .filter(option => option.long) .map(option => option.long); candidateFlags = candidateFlags.concat(moreFlags); - if (command._enablePositionalOptions) break; - } + command = command.parent; + } while (command && !command._enablePositionalOptions); suggestion = suggestSimilar(flag, candidateFlags); } diff --git a/lib/help.js b/lib/help.js index 7f1c2c354..560801bd3 100644 --- a/lib/help.js +++ b/lib/help.js @@ -101,10 +101,10 @@ class Help { if (!this.showGlobalOptions) return []; const globalOptions = []; - cmd._getCommandAndAncestors().slice(1).forEach((ancestorCmd) => { + for (let ancestorCmd = cmd.parent; ancestorCmd; ancestorCmd = ancestorCmd.parent) { const visibleOptions = ancestorCmd.options.filter((option) => !option.hidden); globalOptions.push(...visibleOptions); - }); + } if (this.sortOptions) { globalOptions.sort(this.compareOptions); } @@ -241,9 +241,9 @@ class Help { cmdName = cmdName + '|' + cmd._aliases[0]; } let ancestorCmdNames = ''; - cmd._getCommandAndAncestors().slice(1).forEach((ancestorCmd) => { + for (let ancestorCmd = cmd.parent; ancestorCmd; ancestorCmd = ancestorCmd.parent) { ancestorCmdNames = ancestorCmd.name() + ' ' + ancestorCmdNames; - }); + } return ancestorCmdNames + cmdName + ' ' + cmd.usage(); }