From 920d2e8a9dde3842c9e46fd3261b6113288ebeae Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Thu, 29 Oct 2026 20:06:03 +0000 Subject: [PATCH] fix(cli): quote cobra defaults that KDL reads as booleans or numbers The Cobra exporter wrote some flag defaults as bare KDL values, so `Bool("enabled", true)` produced `default=true` and `Int("parallel", -1)` produced `default=-1`. Usage expects string defaults, so `usage lint` rejected the generated spec. needsQuoting now also quotes true/false/null/inf/-inf/nan, signed numbers, and values containing brackets. Fixes #1594 Co-Authored-By: Claude Sonnet 5.5 Entire-Checkpoint: 01M64MA2XS7BMFE1WFRFEAW1M3 --- integrations/cobra/cobra_usage_test.go | 23 +++++++++++++++++++++++ integrations/cobra/kdl.go | 12 +++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/integrations/cobra/cobra_usage_test.go b/integrations/cobra/cobra_usage_test.go index 4684b7b5a..28f21dbf1 100644 --- a/integrations/cobra/cobra_usage_test.go +++ b/integrations/cobra/cobra_usage_test.go @@ -350,6 +350,29 @@ func TestStringDefaultZero(t *testing.T) { assertContains(t, got, `default="0"`) } +// TestDefaultsQuotedWhenNotIdentifiers checks that defaults KDL would read as a +// boolean, null, or number are quoted so they stay strings. +func TestDefaultsQuotedWhenNotIdentifiers(t *testing.T) { + cmd := &cobra.Command{Use: "app"} + cmd.Flags().Bool("enabled", true, "Enable the feature") + cmd.Flags().Int("parallel", -1, "Maximum parallelism") + cmd.Flags().Float64("ratio", -0.5, "Ratio") + cmd.Flags().String("sign", "+1", "Signed") + cmd.Flags().String("nothing", "null", "Null word") + cmd.Flags().String("infinite", "inf", "Inf word") + cmd.Flags().String("list", "[a]", "Brackets") + + got := Generate(cmd) + + assertContains(t, got, `default="true"`) + assertContains(t, got, `default="-1"`) + assertContains(t, got, `default="-0.5"`) + assertContains(t, got, `default="+1"`) + assertContains(t, got, `default="null"`) + assertContains(t, got, `default="inf"`) + assertContains(t, got, `default="[a]"`) +} + // TestSpecialCharacterEscaping checks that kdlQuoteAlways escapes newlines, tabs, // carriage returns, backslashes, and double quotes. func TestSpecialCharacterEscaping(t *testing.T) { diff --git a/integrations/cobra/kdl.go b/integrations/cobra/kdl.go index 1d2199f95..180e00de7 100644 --- a/integrations/cobra/kdl.go +++ b/integrations/cobra/kdl.go @@ -353,9 +353,19 @@ func needsQuoting(s string) bool { if s[0] >= '0' && s[0] <= '9' { return true } + // A sign followed by a digit (or a dot, which is quoted below) reads as a + // number, so "-1" and "+1" cannot be bare identifiers either. + if (s[0] == '-' || s[0] == '+') && len(s) > 1 && (s[1] >= '0' && s[1] <= '9' || s[1] == '.') { + return true + } + // Keyword-looking values would be parsed as a boolean, null or number. + switch s { + case "true", "false", "null", "inf", "-inf", "nan": + return true + } for _, c := range s { switch c { - case ' ', '\t', '\n', '\r', '"', '\\', '/', '(', ')', '{', '}', ';', '=', '#', '.', ',', ':': + case ' ', '\t', '\n', '\r', '"', '\\', '/', '(', ')', '[', ']', '{', '}', ';', '=', '#', '.', ',', ':': return true } }