@@ -836,6 +836,164 @@ func TestShortFlags(t *testing.T) {
836836 })
837837}
838838
839+ func TestLocalFlags (t * testing.T ) {
840+ t .Parallel ()
841+
842+ t .Run ("local flag on parent not available to child" , func (t * testing.T ) {
843+ t .Parallel ()
844+ child := & Command {
845+ Name : "child" ,
846+ Exec : func (ctx context.Context , s * State ) error { return nil },
847+ }
848+ root := & Command {
849+ Name : "root" ,
850+ Flags : FlagsFunc (func (f * flag.FlagSet ) {
851+ f .Bool ("version" , false , "show version" )
852+ f .Bool ("verbose" , false , "enable verbose output" )
853+ }),
854+ FlagsMetadata : []FlagMetadata {
855+ {Name : "version" , Local : true },
856+ },
857+ SubCommands : []* Command {child },
858+ Exec : func (ctx context.Context , s * State ) error { return nil },
859+ }
860+ // --version on child should fail because it's local to root
861+ err := Parse (root , []string {"child" , "--version" })
862+ require .Error (t , err )
863+ require .ErrorContains (t , err , "flag provided but not defined" )
864+
865+ // --verbose on child should still work (not local)
866+ root2 := & Command {
867+ Name : "root" ,
868+ Flags : FlagsFunc (func (f * flag.FlagSet ) {
869+ f .Bool ("version" , false , "show version" )
870+ f .Bool ("verbose" , false , "enable verbose output" )
871+ }),
872+ FlagsMetadata : []FlagMetadata {
873+ {Name : "version" , Local : true },
874+ },
875+ SubCommands : []* Command {{
876+ Name : "child" ,
877+ Exec : func (ctx context.Context , s * State ) error { return nil },
878+ }},
879+ Exec : func (ctx context.Context , s * State ) error { return nil },
880+ }
881+ err = Parse (root2 , []string {"child" , "--verbose" })
882+ require .NoError (t , err )
883+ assert .True (t , GetFlag [bool ](root2 .state , "verbose" ))
884+ })
885+
886+ t .Run ("local flag works on defining command" , func (t * testing.T ) {
887+ t .Parallel ()
888+ root := & Command {
889+ Name : "root" ,
890+ Flags : FlagsFunc (func (f * flag.FlagSet ) {
891+ f .Bool ("version" , false , "show version" )
892+ }),
893+ FlagsMetadata : []FlagMetadata {
894+ {Name : "version" , Local : true },
895+ },
896+ Exec : func (ctx context.Context , s * State ) error { return nil },
897+ }
898+ err := Parse (root , []string {"--version" })
899+ require .NoError (t , err )
900+ assert .True (t , GetFlag [bool ](root .state , "version" ))
901+ })
902+
903+ t .Run ("local required flag only enforced on defining command" , func (t * testing.T ) {
904+ t .Parallel ()
905+ child := & Command {
906+ Name : "child" ,
907+ Exec : func (ctx context.Context , s * State ) error { return nil },
908+ }
909+ root := & Command {
910+ Name : "root" ,
911+ Flags : FlagsFunc (func (f * flag.FlagSet ) {
912+ f .String ("token" , "" , "auth token" )
913+ }),
914+ FlagsMetadata : []FlagMetadata {
915+ {Name : "token" , Required : true , Local : true },
916+ },
917+ SubCommands : []* Command {child },
918+ Exec : func (ctx context.Context , s * State ) error { return nil },
919+ }
920+ // Child command should not require parent's local required flag
921+ err := Parse (root , []string {"child" })
922+ require .NoError (t , err )
923+
924+ // But root command itself should still require it
925+ root2 := & Command {
926+ Name : "root" ,
927+ Flags : FlagsFunc (func (f * flag.FlagSet ) {
928+ f .String ("token" , "" , "auth token" )
929+ }),
930+ FlagsMetadata : []FlagMetadata {
931+ {Name : "token" , Required : true , Local : true },
932+ },
933+ Exec : func (ctx context.Context , s * State ) error { return nil },
934+ }
935+ err = Parse (root2 , []string {})
936+ require .Error (t , err )
937+ require .ErrorContains (t , err , "required flag" )
938+ })
939+
940+ t .Run ("usage excludes local parent flags from inherited flags" , func (t * testing.T ) {
941+ t .Parallel ()
942+ child := & Command {
943+ Name : "child" ,
944+ Flags : FlagsFunc (func (f * flag.FlagSet ) {
945+ f .Bool ("dry-run" , false , "dry run mode" )
946+ }),
947+ Exec : func (ctx context.Context , s * State ) error { return nil },
948+ }
949+ root := & Command {
950+ Name : "root" ,
951+ Flags : FlagsFunc (func (f * flag.FlagSet ) {
952+ f .Bool ("version" , false , "show version" )
953+ f .Bool ("verbose" , false , "enable verbose output" )
954+ }),
955+ FlagsMetadata : []FlagMetadata {
956+ {Name : "version" , Local : true },
957+ },
958+ SubCommands : []* Command {child },
959+ Exec : func (ctx context.Context , s * State ) error { return nil },
960+ }
961+ err := Parse (root , []string {"child" , "--help" })
962+ require .ErrorIs (t , err , flag .ErrHelp )
963+
964+ usage := DefaultUsage (root )
965+ // --verbose should appear in inherited flags (not local)
966+ assert .Contains (t , usage , "--verbose" )
967+ // --version should NOT appear (local to root, not inherited)
968+ assert .NotContains (t , usage , "--version" )
969+ // --dry-run should appear in local flags
970+ assert .Contains (t , usage , "--dry-run" )
971+ })
972+
973+ t .Run ("local flag with short alias not inherited" , func (t * testing.T ) {
974+ t .Parallel ()
975+ child := & Command {
976+ Name : "child" ,
977+ Exec : func (ctx context.Context , s * State ) error { return nil },
978+ }
979+ root := & Command {
980+ Name : "root" ,
981+ Flags : FlagsFunc (func (f * flag.FlagSet ) {
982+ f .Bool ("version" , false , "show version" )
983+ }),
984+ FlagsMetadata : []FlagMetadata {
985+ {Name : "version" , Short : "V" , Local : true },
986+ },
987+ SubCommands : []* Command {child },
988+ Exec : func (ctx context.Context , s * State ) error { return nil },
989+ }
990+ // Short alias -V should also not work on child
991+ err := Parse (root , []string {"child" , "-V" })
992+ require .Error (t , err )
993+ require .ErrorContains (t , err , "flag provided but not defined" )
994+ })
995+ }
996+
839997func getCommand (t * testing.T , c * Command ) * Command {
840998 require .NotNil (t , c )
841999 require .NotNil (t , c .state )
0 commit comments