Skip to content

Extend UnknownFlags to UnknownFlagsHandling and introduce PassUnknownFlagToArgs (#337)#440

Merged
tomasaschan merged 3 commits into
spf13:masterfrom
ikedam:feature/337_PreserveUnknownFlagsEnum
Jun 6, 2026
Merged

Extend UnknownFlags to UnknownFlagsHandling and introduce PassUnknownFlagToArgs (#337)#440
tomasaschan merged 3 commits into
spf13:masterfrom
ikedam:feature/337_PreserveUnknownFlagsEnum

Conversation

@ikedam

@ikedam ikedam commented Aug 9, 2025

Copy link
Copy Markdown

Close #337

Alternative to #338.

FlagSet.ParseErrorsAllowlist.UnknownFlagsHandling=PassUnknownFlagToArgs behaves like this:

  • Input:
    -xayb -c -z --known-flag known-flag-value --unknown-flag arg0 arg1 arg2
    
    • -x, -y, -z and --unknown-flag are unknown
  • Args() results:
    -xy -z --unknown-flag arg0 arg1 arg2
    

This request deprecates FlagSet.ParseErrorsAllowlist.UnknownFlags and extends it to FlagSet.ParseErrorsAllowlist.UnknownFlagsHandling.

  • FlagSet.ParseErrorsAllowlist.UnknownFlagsHandling = ErrorOnUnknownFlag (Default)
    • Same to FlagSet.ParseErrorsAllowlist.UnknownFlags = false
    • Treats unknown flags error.
  • FlagSet.ParseErrorsAllowlist.UnknownFlagsHandling = IgnoreUnknownFlag
    • Same to FlagSet.ParseErrorsAllowlist.UnknownFlags = true
    • Ignore unknown flags.
  • FlagSet.ParseErrorsAllowlist.UnknownFlagsHandling=PassUnknownFlagToArgs:
    • Pass unknown flags to Args() (described above)

Backward compatibilities:

  • FlagSet.ParseErrorsAllowlist.UnknownFlagsHandling is not set (ErrorOnUnknownFlag) and FlagSet.ParseErrorsAllowlist.UnknownFlags = true:
    • Same to FlagSet.ParseErrorsAllowlist.UnknownFlagsHandling = ErrorOnUnknownFlag
    • Ignore unknown flags.
  • Also same for ParseErrorsWhitelist

Comment thread flag_test.go Outdated
if f.Parsed() {
t.Fatal("f.Parse() = true before Parse")
}
f.ParseErrorsAllowlist.UnknownFlagsHandling = UnknownFlagsHandlingPassUnknownToArgs

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Comment thread flag.go Outdated
Comment on lines +144 to +152
// UnknownFlagsHandlingErrorOnUnknown will return an error if an unknown flag is found
UnknownFlagsHandlingErrorOnUnknown UnknownFlagsHandling = iota
// UnknownFlagsHandlingIgnoreUnknown will ignore unknown flags and continue parsing rest of the flags
UnknownFlagsHandlingIgnoreUnknown
// UnknownFlagsHandlingPassUnknownToArgs will treat unknown flags as non-flag arguments.
// Combined shorthand flags mixed with known ones and unknown ones results
// combined flags only with unknown ones.
// E.g. -fghi results -gh if only `f` and `i` are known.
UnknownFlagsHandlingPassUnknownToArgs

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Reeeally long names...
I worry names like ErrorOnUnknown would cause name conflict.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe ErrorOnUnknownFlag? Along with IgnoreUnknownFlag and PassUnknownFlagToArgs. I don't think they can be made shorter than that without losing meaning, but I don't think we need to be overly cautious about naming conflicts here.

If you really want to guard against it, another variant is to namespace these somehow - either with a struct value where the enum values are fields on the struct, or by just moving them to an appropriately named subpackage.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Renamed in c0bdd63

@tomasaschan tomasaschan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I like this version a lot better than #338.

(The passing-error-as-control-flow thing smells a little, but the parseLongArg method was a mess before this change, so that's not your fault... Something to look out for in 2.0, I guess.)

Needs a rebase (I haven't looked into why) and probably shouldn't be merged until we've decided we don't have anything else we want to get into 1.0 before cutting 1.1.

Comment thread flag.go Outdated
Comment on lines +144 to +152
// UnknownFlagsHandlingErrorOnUnknown will return an error if an unknown flag is found
UnknownFlagsHandlingErrorOnUnknown UnknownFlagsHandling = iota
// UnknownFlagsHandlingIgnoreUnknown will ignore unknown flags and continue parsing rest of the flags
UnknownFlagsHandlingIgnoreUnknown
// UnknownFlagsHandlingPassUnknownToArgs will treat unknown flags as non-flag arguments.
// Combined shorthand flags mixed with known ones and unknown ones results
// combined flags only with unknown ones.
// E.g. -fghi results -gh if only `f` and `i` are known.
UnknownFlagsHandlingPassUnknownToArgs

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe ErrorOnUnknownFlag? Along with IgnoreUnknownFlag and PassUnknownFlagToArgs. I don't think they can be made shorter than that without losing meaning, but I don't think we need to be overly cautious about naming conflicts here.

If you really want to guard against it, another variant is to namespace these somehow - either with a struct value where the enum values are fields on the struct, or by just moving them to an appropriately named subpackage.

Comment thread flag.go Outdated
Comment on lines +166 to +175
func (a *ParseErrorsAllowlist) getUnknownFlagsHandling() UnknownFlagsHandling {
// if UnknownFlagsHandling is set, use it
if a.UnknownFlagsHandling != UnknownFlagsHandlingErrorOnUnknown {
return a.UnknownFlagsHandling
}

if a.UnknownFlags {
return UnknownFlagsHandlingIgnoreUnknown
}
return UnknownFlagsHandlingErrorOnUnknown

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice!

@ikedam ikedam force-pushed the feature/337_PreserveUnknownFlagsEnum branch from f16d4d7 to d1d7aee Compare March 6, 2026 16:56
@ikedam

ikedam commented Mar 6, 2026

Copy link
Copy Markdown
Author

First, rebased and resolved the conflict.
Conflicted with #446 .

Needs to support both ParseErrorsAllowlist and ParseErrorsWhitelist, and resolved as d1d7aee .
The conflict was left unresolved in the previous commit and fixed in this one so reviewers can see exactly how it was resolved.

* `UnknownFlagsHandlingErrorOnUnknown` to `ErrorOnUnknownFlag` and so on
@ikedam ikedam changed the title Extend UnknownFlags to UnknownFlagsHandling and introduce UnknownFlagsHandlingPassUnknownToArgs (#337) Extend UnknownFlags to UnknownFlagsHandling and introduce PassUnknownFlagToArgs (#337) Mar 6, 2026
@ikedam ikedam requested a review from tomasaschan March 6, 2026 17:10

@tomasaschan tomasaschan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm sorry this took so long to get to reviewing. I have a couple of questions, but given this behavior is opt-in I think it would be OK to merge this to make it easier to test in real applications, and roll forward when more edge cases pop up.

Let me know if you prefer a merge now or to address those questions first.

Comment thread flag.go
Comment on lines 1075 to +1077
return stripUnknownFlagValue(a), nil
case unknownFlagsHandling == PassUnknownFlagToArgs:
return a, &unknownFlagError{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't understand why the previous case calls stripUnknownFlagValue and this is one does not.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

We can consider the case of --unknown somevalue.
--unknown is an unknown flag, and we cannot reliably tell whether somevalue is a flag value or a positional argument.

With PassUnknownFlagToArgs, we do not need to classify somevalue; we simply leave the argument stream intact and let the caller receive everything as positional args. So we don't need to call stripUnknownFlagValue().

With IgnoreUnknownFlag, the existing behavior treats the next token as a flag value (when it does not look like a flag) and calls stripUnknownFlagValue() to strip it. This behavior is inherited from the existing IgnoreUnknownFlag implementation.

Comment thread flag.go
Comment on lines +1144 to +1155
if len(shorthands) > 2 && shorthands[1] == '=' {
outShorts = ""
err = &unknownFlagError{
UnknownFlags: shorthands,
}
return
}
// '-fgh': pass only the first switch
err = &unknownFlagError{
UnknownFlags: shorthands[0:1],
}
return

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What about -fg=g were both f and g are shorthands, but only g accept a value? Or -fgh (same)?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Same here, differs in the next call.

For an unknown flag, parseShortArg only need to choose between:

  • strip only the first letter, or
  • strip all the argument

Both -fg=g and -fgh are the former case: only strips the first letter (f) as an unknown flag, and pass the rest (g=g or gh).
The rest will be handled in the next parseSingleShortArg() call.

In the next parseSingleShortArg(), g=g results the latter case, and gh results the former case.

@tomasaschan tomasaschan merged commit 9912b67 into spf13:master Jun 6, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Preserve unknown flags

2 participants