diff --git a/builtins/builtins.go b/builtins/builtins.go index 608d7d62..23328eb5 100644 --- a/builtins/builtins.go +++ b/builtins/builtins.go @@ -63,7 +63,15 @@ func (c Command) Register() { name := c.Name factory := c.MakeFlags normalize := c.NormalizeArgs - metaRegistry[name] = CommandMeta{Name: name, Description: c.Description, Help: c.Help} + + // Probe whether the command registers any flags so we can record it + // in metadata (used by tests to enforce help-consistency invariants). + probe := pflag.NewFlagSet(name, pflag.ContinueOnError) + probe.SetOutput(io.Discard) + factory(probe) + hasFlags := probe.HasFlags() + + metaRegistry[name] = CommandMeta{Name: name, Description: c.Description, Help: c.Help, HasFlags: hasFlags} addToRegistry(name, func(ctx context.Context, callCtx *CallContext, args []string) Result { fs := pflag.NewFlagSet(name, pflag.ContinueOnError) fs.SetOutput(io.Discard) // handler formats errors itself @@ -221,6 +229,7 @@ type CommandMeta struct { Name string Description string Help string + HasFlags bool // true when MakeFlags registers at least one flag } var metaRegistry = map[string]CommandMeta{} diff --git a/builtins/tests/help/help_test.go b/builtins/tests/help/help_test.go index ccb9d026..77faf997 100644 --- a/builtins/tests/help/help_test.go +++ b/builtins/tests/help/help_test.go @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/require" "mvdan.cc/sh/v3/syntax" + "github.com/DataDog/rshell/builtins" "github.com/DataDog/rshell/internal/interpoption" "github.com/DataDog/rshell/interp" ) @@ -290,3 +291,20 @@ func TestHelpNoStderrOnSuccess(t *testing.T) { assert.Equal(t, 0, code) assert.Empty(t, stderr) } + +// --- Invariant: Help field only on NoFlags commands --- + +func TestHelpFieldOnlyOnNoFlagsCommands(t *testing.T) { + // Trigger registration so Names()/Meta() are populated. + _, _ = interp.New() + + for _, name := range builtins.Names() { + meta, ok := builtins.Meta(name) + require.True(t, ok) + if meta.Help != "" && meta.HasFlags { + t.Errorf("%s: Help field must not be set on commands that register flags — "+ + "use --help instead so that 'help %s' and '%s --help' produce the same output", + name, name, name) + } + } +} diff --git a/builtins/uname/uname.go b/builtins/uname/uname.go index cc06dc01..491f5738 100644 --- a/builtins/uname/uname.go +++ b/builtins/uname/uname.go @@ -51,12 +51,7 @@ import ( var Cmd = builtins.Command{ Name: "uname", Description: "print system information", - Help: `uname: uname [-asnrvm] - Print system information. - - With no flags, print the kernel name (same as -s). - Reads from /proc/sys/kernel/ (configurable via --proc-path).`, - MakeFlags: makeFlags, + MakeFlags: makeFlags, } // kernelFiles maps each flag letter to the proc pseudo-file that