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

feat!: create result.values with null prototype #111

Merged
merged 9 commits into from
Apr 17, 2022
22 changes: 6 additions & 16 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const {
ArrayPrototypeShift,
ArrayPrototypeSlice,
ArrayPrototypePush,
ObjectDefineProperty,
ObjectEntries,
ObjectPrototypeHasOwnProperty: ObjectHasOwn,
StringPrototypeCharAt,
Expand Down Expand Up @@ -117,19 +116,9 @@ function checkOptionUsage(longOption, optionValue, options,
*/
function storeOption(longOption, optionValue, options, values) {
if (longOption === '__proto__') {
return;
return; // No. Just no.
Copy link
Member

@ljharb ljharb Apr 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not actually sure we should be returning here - we can just set a data property of the string "__proto__", no?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern is it is fine in the values we return, but is it a potential foot-gun when the author copies it out into some full object? Or does who-knows-what? We are going to great lengths to make this as safe as we can, why would we allow it?

(I might have missed the point though. Not sure what you meant by string "proto".)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would only be a footgun if a user is relying on the (deletable) Object.prototype.__proto__ accessor. Otherwise, it'd just be a string property.

If we want to disallow it, why not throw in strict mode?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existing behaviour. I care enough about it to add a comment showing it is deliberate. But not to do work on it in this PR. 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol totally fine, we can look at it later.

}

// Can be removed when value has a null prototype
const safeAssignProperty = (obj, prop, value) => {
ObjectDefineProperty(obj, prop, {
value,
writable: true,
enumerable: true,
configurable: true
});
};

// We store based on the option value rather than option type,
// preserving the users intent for author to deal with.
const newValue = optionValue ?? true;
Expand All @@ -138,13 +127,14 @@ function storeOption(longOption, optionValue, options, values) {
// values[longOption] starts out not present,
// first value is added as new array [newValue],
// subsequent values are pushed to existing array.
if (ObjectHasOwn(values, longOption)) {
// (note: values has null prototype, so simpler usage)
if (values[longOption]) {
ArrayPrototypePush(values[longOption], newValue);
} else {
safeAssignProperty(values, longOption, [newValue]);
values[longOption] = [newValue];
}
} else {
safeAssignProperty(values, longOption, newValue);
values[longOption] = newValue;
}
}

Expand Down Expand Up @@ -184,7 +174,7 @@ const parseArgs = (config = { __proto__: null }) => {
);

const result = {
values: {},
values: { __proto__: null },
positionals: []
};

Expand Down
14 changes: 7 additions & 7 deletions test/dash.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ const { parseArgs } = require('../index.js');
// A different usage and example is `git switch -` to switch back to the previous branch.

test("dash: when args include '-' used as positional then result has '-' in positionals", (t) => {
const passedArgs = ['-'];
const expected = { values: {}, positionals: ['-'] };
const args = ['-'];
const expected = { values: { __proto__: null }, positionals: ['-'] };

const result = parseArgs({ args: passedArgs });
const result = parseArgs({ args });

t.deepEqual(result, expected);
t.end();
});

// If '-' is a valid positional, it is symmetrical to allow it as an option value too.
test("dash: when args include '-' used as space-separated option value then result has '-' in option value", (t) => {
const passedArgs = ['-v', '-'];
const passedOptions = { v: { type: 'string' } };
const expected = { values: { v: '-' }, positionals: [] };
const args = ['-v', '-'];
const options = { v: { type: 'string' } };
const expected = { values: { __proto__: null, v: '-' }, positionals: [] };

const result = parseArgs({ args: passedArgs, options: passedOptions });
const result = parseArgs({ args, options });

t.deepEqual(result, expected);
t.end();
Expand Down