From 6fc778cef500fa5e4a3e80053bd50c214f1e06b3 Mon Sep 17 00:00:00 2001 From: skovranek <59619403+skovranek@users.noreply.github.com> Date: Tue, 15 Sep 2026 18:05:16 -0400 Subject: [PATCH 1/2] Add JSONC input mode for jq checks --- checks/jq.go | 11 +++++++++- checks/jq_test.go | 52 +++++++++++++++++++++++++++++++++++++++++++++++ client/lessons.go | 2 +- go.mod | 1 + go.sum | 2 ++ 5 files changed, 66 insertions(+), 2 deletions(-) diff --git a/checks/jq.go b/checks/jq.go index aad5f34..ea02205 100644 --- a/checks/jq.go +++ b/checks/jq.go @@ -9,6 +9,7 @@ import ( api "github.com/bootdotdev/bootdev/client" "github.com/goccy/go-json" "github.com/itchyny/gojq" + "github.com/tailscale/hujson" ) func prettyPrintStdoutJqTest(test api.StdoutJqTest, variables map[string]string) string { @@ -66,9 +67,17 @@ func runStdoutJqQuery(stdout string, test api.StdoutJqTest, variables map[string func parseJqInput(stdout string, inputMode string) (any, error) { mode := strings.ToLower(strings.TrimSpace(inputMode)) - if mode != "json" && mode != "jsonl" { + if mode != "json" && mode != "jsonl" && mode != "jsonc" { mode = "json" } + if mode == "jsonc" { + // HuJSON requires a newline to terminate a final line comment. + standardJSON, err := hujson.Standardize([]byte(stdout + "\n")) + if err != nil { + return nil, err + } + stdout = string(standardJSON) + } decoder := json.NewDecoder(strings.NewReader(stdout)) decoder.UseNumber() diff --git a/checks/jq_test.go b/checks/jq_test.go index f86667b..050073d 100644 --- a/checks/jq_test.go +++ b/checks/jq_test.go @@ -96,6 +96,58 @@ func TestParseJqInputRejectsMultipleJSONValuesInJSONMode(t *testing.T) { } } +func TestRunStdoutJqQueryJSONC(t *testing.T) { + tests := []struct { + name string + stdout string + query string + want []string + }{ + {"queries normalized JSONC", `{ + // Users to query + "users": [/* primary user */ {"name":"Boots",},], + }`, `.users[].name`, []string{`"Boots"`}}, + {"large integer", `{"id":9007199254740993,}`, `.id`, []string{`9007199254740993`}}, + {"trailing line comment", `{"name":"Boots"} // comment`, `.name`, []string{`"Boots"`}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := runStdoutJqQuery(tt.stdout, api.StdoutJqTest{InputMode: "jsonc", Query: tt.query}, nil) + if got.Error != "" { + t.Fatalf("unexpected error: %s", got.Error) + } + if !reflect.DeepEqual(got.Results, tt.want) { + t.Fatalf("results = %v, want %v", got.Results, tt.want) + } + }) + } +} + +func TestRunStdoutJqQueryReturnsJSONCParseError(t *testing.T) { + got := runStdoutJqQuery(`{"name":"Boots"} /* unterminated`, api.StdoutJqTest{InputMode: "jsonc", Query: "."}, nil) + if got.Error == "" || len(got.Results) != 0 || got.Query != "." { + t.Fatalf("expected query with parse error and no results, got %#v", got) + } +} + +func TestParseJqInputJSONCModeIsolation(t *testing.T) { + for _, mode := range []string{"json", "jsonl"} { + for _, stdout := range []string{`{/* comment */ "name":"Boots"}`, `{"name":"Boots",}`} { + t.Run(mode+stdout, func(t *testing.T) { + if _, err := parseJqInput(stdout, mode); err == nil { + t.Fatal("expected strict input parsing to reject JSONC") + } + }) + } + } + t.Run("normalized input mode", func(t *testing.T) { + got, err := parseJqInput(`/* comment */ true`, " JSONC \t") + if err != nil || got != true { + t.Fatalf("got %v, %v; want true, nil", got, err) + } + }) +} + func TestValFromJqPath(t *testing.T) { tests := []struct { name string diff --git a/client/lessons.go b/client/lessons.go index c51ece0..7a5078f 100644 --- a/client/lessons.go +++ b/client/lessons.go @@ -60,7 +60,7 @@ type CLICommandTest struct { } type StdoutJqTest struct { - InputMode string `yaml:"inputMode"` // "json" or "jsonl" + InputMode string `yaml:"inputMode"` // "json", "jsonl", or "jsonc" Query string `yaml:"query"` ExpectedResults []JqExpectedResult `yaml:"expectedResults"` } diff --git a/go.mod b/go.mod index cdc47b7..af8caa1 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 + github.com/tailscale/hujson v0.0.0-20241010212012-29efb4a0184b go.yaml.in/yaml/v3 v3.0.4 golang.org/x/mod v0.32.0 golang.org/x/term v0.39.0 diff --git a/go.sum b/go.sum index f2a46b0..e01cb80 100644 --- a/go.sum +++ b/go.sum @@ -90,6 +90,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tailscale/hujson v0.0.0-20241010212012-29efb4a0184b h1:MNaGusDfB1qxEsl6iVb33Gbe777IKzPP5PDta0xGC8M= +github.com/tailscale/hujson v0.0.0-20241010212012-29efb4a0184b/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= From ed40d8ba59a184c4b0fabcb4d956e45b8bd20e7b Mon Sep 17 00:00:00 2001 From: skovranek <59619403+skovranek@users.noreply.github.com> Date: Tue, 15 Sep 2026 18:08:20 -0400 Subject: [PATCH 2/2] update minor version --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 2959091..cde949b 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v1.32.3 +v1.33.0