Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Fix option namespace pollution #951

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 49 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ function Command(name) {
this._allowUnknownOption = false;
this._args = [];
this._name = name || '';
this.optMap = new Map();
this._storeOptionsAsProperties = true; // legacy behaviour by default
}

/**
Expand Down Expand Up @@ -376,6 +378,11 @@ Command.prototype.option = function(flags, description, fn, defaultValue) {
oname = option.name(),
name = option.attributeName();

if (self._storeOptionsAsProperties && self[name] !== undefined) {
console.log("Setup warning: option name '%s' conflicts with internal property", name);
console.log(' Either change option name, or setFeatureFlags with storeOptionsAsProperties:false and update code');
}

// default as 3rd arg
if (typeof fn !== 'function') {
if (fn instanceof RegExp) {
Expand All @@ -396,7 +403,10 @@ Command.prototype.option = function(flags, description, fn, defaultValue) {
if (!option.bool) defaultValue = true;
// preassign only if we have a default
if (defaultValue !== undefined) {
self[name] = defaultValue;
self.optMap.set(name, defaultValue);
if (self._storeOptionsAsProperties) {
self[name] = defaultValue;
}
option.defaultValue = defaultValue;
}
}
Expand All @@ -409,22 +419,28 @@ Command.prototype.option = function(flags, description, fn, defaultValue) {
this.on('option:' + oname, function(val) {
// coercion
if (val !== null && fn) {
val = fn(val, self[name] === undefined ? defaultValue : self[name]);
val = fn(val, self.optMap.has(name) ? self.optMap.get(name) : defaultValue);
}

// unassigned or bool
if (typeof self[name] === 'boolean' || typeof self[name] === 'undefined') {
if (typeof self.optMap.get(name) === 'boolean' || !self.optMap.has(name)) {
// if no value, bool true, and we have a default, then use it!
if (val == null) {
self[name] = option.bool
self.optMap.set(name, option.bool
? defaultValue || true
: false;
: false);
} else {
self[name] = val;
self.optMap.set(name, val);
}
if (self._storeOptionsAsProperties) {
self[name] = self.optMap.get(name);
}
} else if (val !== null) {
// reassign
self[name] = val;
self.optMap.set(name, val);
if (self._storeOptionsAsProperties) {
self[name] = val;
}
}
});

Expand All @@ -443,6 +459,23 @@ Command.prototype.allowUnknownOption = function(arg) {
return this;
};

/**
* Set featureFlags.
*
* @param {Object} boolean values for feature flags
* @return {Command} for chaining
* @api public
*/
Command.prototype.setFeatureFlags = function(flags) {
const storeOptionsAsPropertiesStr = 'storeOptionsAsProperties';
if (typeof flags === 'object') {
if (Object.prototype.hasOwnProperty.call(flags, storeOptionsAsPropertiesStr)) {
this._storeOptionsAsProperties = flags[storeOptionsAsPropertiesStr];
}
}
return this;
};

/**
* Parse `argv`, settings options and invoking commands when defined.
*
Expand Down Expand Up @@ -771,13 +804,17 @@ Command.prototype.parseOptions = function(argv) {
* @api public
*/
Command.prototype.opts = function() {
var result = {},
len = this.options.length;
var result = {};

for (var i = 0; i < len; i++) {
var key = this.options[i].attributeName();
result[key] = key === this._versionOptionName ? this._version : this[key];
for (const [key, val] of this.optMap.entries()) {
result[key] = val;
}

// Preserve legacy behaviour that version added into result.
if (this._versionOptionName) {
result[this._versionOptionName] = this._version;
}

return result;
};

Expand Down