Skip to content

Is strict actually a goal? #11

Description

@shadowspawn

strict {Boolean} (Optional) A Boolean on wheather or not to throw an error when unknown args are encountered

The current interface is leaning towards the minimal (magic) configuration goal of the original proposal. I do not currently see a way of declaring an option as being known but not taking a value, so there is no way to identify unknown options?

The FAQ includes:

Do unknown arguments raise an error? Are they parsed? Are they treated as positional arguments?
no, they are parsed, not treated as positionals

Related:

(A different case is --foo=bar when foo is not specified to take an argument value. Not including that as part of strict for current question. nodejs/node#35015 (comment))

Activity

  1. ljharb commented on Nov 14, 2021

    @ljharb
    Member

    It should be a goal to error and show help output when an unknown argument is passed, just like when a known argument is passed with an incorrect value.

  2. added this to the Merge into Node.js milestone on Dec 4, 2021
  3. darcyclarke commented on Jan 16, 2022

    @darcyclarke
    Member

    @ljharb

    "It should be a goal to error and show help output when an unknown argument is passed"

    Agree.

    "just like when a known argument is passed with an incorrect value."

    Disagree. Also, a bit confused; I don't see any way the current spec allows you to define a value which validates against a value passed. The goal of this spec, which differentiated it from the previous work, was that you'd "bring your own validation" in reference to values & types.

    @shadowspawn the F.A.Q. is fairly conversational in nature (as it literally spawned out of a discussion between myself & @isaacs); with that in mind, it's correct in stating that we should parse (to the best of our abilities & with the options provided) all input. strict mode should throw an error when args are found that aren't explicitly defined/"known" in withValues (or... maybe some other option which isn't spec'd yet?)

  4. darcyclarke commented on Jan 16, 2022

    @darcyclarke
    Member

    (A different case is --foo=bar when foo is not specified to take an argument value. Not including that as part of strict for current question. nodejs/node#35015 (comment))

    This makes sense... I'm actually a +1 to just kill strict for now. End-user can decide to throw if they see a flag they weren't expecting.

  5. ljharb commented on Jan 17, 2022

    @ljharb
    Member

    @darcyclarke i meant conceptually - obviously this package wouldn't provide validation, but what's the use case for anyone wanting to permit unknown values?

  6. darcyclarke commented on Jan 17, 2022

    @darcyclarke
    Member

    @ljharb I know of usecases, today, where you'd want to capture unknown/undefined flags (to the root program) & pass those along to another process or use in some form later (ex. npm's support of infinite config 👀 🤦🏻). Not saying it's a best practice, just that usecases exist.

    Contrived Example:

    node mkdir.js ./path/to/new/dir/ --force --verbose --parents
    // mkdir.js
    
    const knownOpts = ['force']
    const { flags, positionals } = parseArgs({ withValue: knownOpts })
    const args = Object.keys(flags).filter(f => knownOpts[f])
    const cmd = (flags.force) ? 'sudo mkdir' : 'mkdir'
    
    process('child_process').spawnSync(cmd, [...args, ...positionals])

    In the above, I expect & am aware of --force but all other flags & positionals get passed along to a child process (note: mkdir will then throw if it's not aware of those).

  7. ljharb commented on Jan 17, 2022

    @ljharb
    Member

    Must we support every possible use case? Or perhaps it’d be better to establish good defaults, since nobody is forced to use this solution, and it’s perfectly fine if not everyone can.

  8. bcoe commented on Jan 22, 2022

    @bcoe
    Collaborator

    Are we okay with dropping strict behavior for MVP? we can change this decision depending on code review on Node.js.

  9. ljharb commented on Jan 22, 2022

    @ljharb
    Member

    I'm still not sure why anyone would want non-strict - it seems much better to be strict by default, and let developers opt in to allowing unknown arguments.

  10. shadowspawn commented on Jan 22, 2022

    @shadowspawn
    CollaboratorAuthor

    Reference, two of the summary message from previous thread covering strictness and correctness:

  11. shadowspawn commented on Jan 22, 2022

    @shadowspawn
    CollaboratorAuthor

    Short version: I am ok with wrapping a release without strict detection of unknown arguments. But I am willing to invest in strict mode for the misuse errors in first instance, and then for unknowns.

    Long version

    A big part of the original vision is the minimal config. This is incompatible with strict since flags are discovered at runtime and can't be "unknown", typos can not be detected by the parser.

    I think the minimal config is appealing and useful for quick programs used mainly by their own author. I am reassured that @bcoe thinks the non-strict mode is useful with his experience with Yargs and support issues.

    To make parseArgs useful to a wider audience, I think strict is important. This expands the audience to safer usage where the author can rely on common errors being detected and handled without writing their own code, and the end-user can rely on the program blocking many cases of accidental misuse. I want the utilities I write to become strict as they mature.

    I don't feel it is worth silently returning an error message when not strict. (This idea is raised in a few places in the context of strict, and idea that parsing always succeeds.) I think that once an error has been detected then the parsing is tainted. So strict is a yes/no mode, the strict parse succeeded or it failed, and an exception is appropriate.

    I suggest as a proof of concept we implement strict to throw for the error cases for a flag used with a value, and withValue option used without a value. These are within the scope of what is already described in the README and encountered in the current code and discussed at length in open issues. This is not what the README describes strict as for, but I feel it is within the spirit!

    (As an aside, I continue to be impressed by how flexible Minimist is. Looks like is is capable of implementing strict for unknown arguments by using opts.unknown and declaring the expected options.)

  12. ljharb commented on Jan 23, 2022

    @ljharb
    Member

    I’ve used yargs in a half dozen projects, and always ended up adding strict later. Without strict, adding any argument is a breaking change. Strict mode is the only way semver-minor becomes practical.

  13. bcoe commented on Jan 23, 2022

    @bcoe
    Collaborator

    I’ve used yargs in a half dozen projects, and always ended up adding strict later.
    I suggest as a proof of concept we implement strict to throw for the error cases for a flag used with a value, and withValue

    I'm convinced we should have strict for MVP, but I'd make the case for it being an opt in, as with yargs.

    Would you be okay with this compromise @ljharb (needing to set a strict: true in the options).

  14. ljharb commented on Jan 23, 2022

    @ljharb
    Member

    @bcoe can you actually make the case? in what use case or realm of computing is non-strict a desired default?

  15. 26 remaining items

  16. bakkot commented on Mar 1, 2022

    @bakkot
    Collaborator

    The caller can easily check, no loss of information, and no new pattern in result.

    Well, they can, but will they? This is a persistent problem in JS: when the types are not as you expect, things end up giving the error at the wrong place.

    How bad this is will depend on the results convention, but for simplicity I'm imagining something like what's proposed in that issue, where all the values for all options, boolean or string, end up in the same place. With such a design, if you write a script expecting --path=/a/b, but the user just writes --path part, the script is going to get { path: true }. If the script author forgets to check the type, and they do something like fetch(path), that's (hopefully!) going to fail with a message like "failed to fetch true". Which is not an especially useful error.


    I think it's worth considering this with use cases in mind. For a rapid prototype or throwaway script, ending up with the wrong type for a configured option seems like it's just going to lead to more confusing errors, happening somewhat later in the execution. For authors hoping to provide custom errors, they now have to remember to check the typeof of every returned option. Neither of those seem like they're well served by mixing options-used-correctly with options-used-incorrectly.

    What's the benefit of mixing the options-used-correctly with the options-used-incorrectly?

    (Note that I'm only talking about the case where the script author does specify the option, but the user of the script misuses it, e.g. by passing a value to a boolean flag or failing to pass a value to a value-taking option.)

  17. shadowspawn commented on Mar 1, 2022

    @shadowspawn
    CollaboratorAuthor

    Well, they can, but will they? This is a persistent problem in JS: when the types are not as you expect, things end up giving the error at the wrong place.

    This wider issue and comments from you and @ljharb is why I think we are currently leaning towards making strict:true the default, although the original vision was definitely strict:false with BYO validation.

    What's the benefit of mixing the options-used-correctly with the options-used-incorrectly?

    A range of responses, see if any resonate. I don't think your suggestion is bad, but I don't currently think it is better.

    1. The strict:false mode allows zero-config parsing. Using a variant of the minimist example, with proposed behaviours and results:
    // console.log(parseArgs({ strict: false });
    $ node example/parse.js -abc --beep=boop foo bar baz
    {
      passedOptions: { a: true, b: true, c: true, beep: true },
      values: { a: true, b: true, c: true, beep: 'boop' }
      positionals: ['foo', 'bar', 'baz']
    }
    1. One way of looking at this is "the way they're stored matches the intention of the user, not the configurer, which will ensure the configurer can most accurately respond to the user's intentions."
      Behaviour for zero config --foo=a ? #24 (comment)

    2. Counter question. If the author is not checking for errors, what's the benefit of quietly sticking some of the options somewhere else? This does not directly lead to better errors in the right place either.

  18. bakkot commented on Mar 1, 2022

    @bakkot
    Collaborator

    The strict:false mode allows zero-config parsing.

    You can have zero-config parsing either way. With the design I'm proposing, the only difference is that you'd read from the things-which-don't-match-config property of the result rather than the things-which-match-config property.

    the way they're stored matches the intention of the user, not the configurer, which will ensure the configurer can most accurately respond to the user's intentions

    I feel like the configurer can respond just fine either way? The only difference is whether we make it clear where the user's intentions don't match the configurer's.

    If the author is not checking for errors, what's the benefit of quietly sticking some of the options somewhere else? This does not directly lead to better errors in the right place either.

    If the author isn't checking for errors, then it doesn't necessarily lead to better errors, but it probably leads to earlier errors: you'll usually get a message about something being missing or undefined rather than having the wrong value entirely. And you're less likely to end up doing something completely wrong, like writing to a file named "true".

    Conversely, if the author is checking for errors, it's easier to do so when all of the things-which-don't-match-config are in the same place, separated from the things-which-do-match-config.


    I'm not all that attached to the idea of splitting out unknown properties with strict: false, I should say. It seems like it helps, and does not hurt, both of the use cases we've identified, so I'm in favor of it, but strict: false is going to be require care to use anyway.

    But I do feel strongly that strict: true should enforce (with an error) that options which are configured to take values are in fact given values.

  19. shadowspawn commented on Mar 1, 2022

    @shadowspawn
    CollaboratorAuthor

    Thoughtful comments @bakkot thanks. I'm going to stop commenting on the idea of splitting out the un-authored properties to focus on the strict:true handling.

  20. shadowspawn commented on Mar 1, 2022

    @shadowspawn
    CollaboratorAuthor

    (Explicitly adding my +1 to two of the cases I listed. 😄 )

    • option of type='string' used like a boolean option e.g. lone --string
      • I strongly support this throwing an error when strict:true
    • option of type='boolean' used like a string option e.g. --boolean=bogus
      • I strongly support this throwing an error when strict:true

    The lack of detection of --boolean=bogus is one of the things that I think broke the original proposal. See for example: nodejs/node#35015 (comment)

    @aaronccasanova wrote:

    I also think this is the cleanest way to introduce strict mode by avoiding littering the parser with strict checks and allowing us to slip in the unknown option validation in the store option util

    As for implementation, these two can both be detected in the same place as the unknown options in storeOptionValue, and won't litter the parser. (No slight intended, I care about that too.)

  21. shadowspawn commented on Mar 1, 2022

    @shadowspawn
    CollaboratorAuthor

    group of short options with an option taking a value in the middle

    Short version: in strict mode, this could throw same missing value error as for lone --string (identifying the short option which was expecting a value). I am +1 for that.

    Long version

    I was writing up a long story when I realised this case does not need to be identified to the user as a different error. Here is the journey...

    1. I'll run through an example of the parsing that getopts and Commander do with this edge case. (I implementing the current Commander behaviour, so can speak to the reasoning if desired.)

    If parseArgs does the same and does not generate an error in strict mode:

    const result = parseArgs({
        args: ['-foo'],
        strict: true,
        options: { 
            f: { type: 'boolean' },
            o: { type: 'sting' }
        }
    });
    console.log(result.values);
    
    { f: true, o: 'o' }
    
    1. If the implementation blindly expanded all middle shorts in a group as if they were boolean, we would effectively be parsing ['-f', '-o', '-o'] which would generate an error in strict mode due to the missing value for the first use of -o. I am ok with that outcome, but not with what the parsing would produce in strict:false mode.

    However, this does suggest the strict:true mode does not need a different error, whatever the strict:false mode does. I like that... 💡

    Implementation: this will require extra code, but I already had this possibility in mind in #68 and been thinking further about it since.

  22. shadowspawn commented on Mar 30, 2022

    @shadowspawn
    CollaboratorAuthor

    🔨 Moving towards MVP Milestone 1 (#87)

    I propose:

    1. implement strict:true. Throw errors for:
    • unknown option encountered
    • option of type:'string' used like a boolean option e.g. lone --string
    • option of type:'boolean' used like a string option e.g. --boolean=bogus
    1. strict by default (!)
    2. no error thrown or returned for strict:false, left entirely to author

    Why default to strict? Some of the active people here are very keen and eloquent! No champions here for strict:false. Experience for (almost) zero-config is reasonable and visible opt-out:

    const { values } = parseArgs({ strict: false });
    

    Why not that error about short option group with string in middle? Rare. Get more important things done.

  23. ljharb commented on Mar 30, 2022

    @ljharb
    Member

    In strict, what about multiple values for something configured not to be multiple?

  24. shadowspawn commented on Mar 30, 2022

    @shadowspawn
    CollaboratorAuthor

    Not an error, last one wins.

  25. ljharb commented on Mar 30, 2022

    @ljharb
    Member

    Seems like in strict mode that should be an error, no?

  26. bakkot commented on Mar 30, 2022

    @bakkot
    Collaborator

    No, definitely not. Arguments are always last-wins.

  27. shadowspawn commented on Mar 30, 2022

    @shadowspawn
    CollaboratorAuthor

    (For interest, this was raised and discussed in previous PR to node: nodejs/node#35015 (comment) )

  28. shadowspawn commented on Apr 13, 2022

    @shadowspawn
    CollaboratorAuthor

    Strict is indeed a goal, and enabled by default! Landed in #74.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions