diff --git a/cmd/cue/cmd/fmt.go b/cmd/cue/cmd/fmt.go index 3dc1fc73f63..f5e590f8fec 100644 --- a/cmd/cue/cmd/fmt.go +++ b/cmd/cue/cmd/fmt.go @@ -15,7 +15,10 @@ package cmd import ( + "bytes" + "fmt" "github.com/spf13/cobra" + "os" "cuelang.org/go/cue/ast" "cuelang.org/go/cue/build" @@ -56,6 +59,9 @@ func newFmtCmd(c *Command) *cobra.Command { cfg.Format = opts cfg.Force = true + check := flagCheck.Bool(cmd) + var modifiedFiles []string + for _, inst := range builds { if inst.Err != nil { switch { @@ -67,7 +73,7 @@ func newFmtCmd(c *Command) *cobra.Command { } } for _, file := range inst.BuildFiles { - files := []*ast.File{} + var files []*ast.File d := encoding.NewDecoder(file, &cfg) for ; !d.Done(); d.Next() { f := d.File() @@ -86,6 +92,11 @@ func newFmtCmd(c *Command) *cobra.Command { exitOnErr(cmd, err, true) } + var formatted bytes.Buffer + if check { + cfg.Out = &formatted + } + e, err := encoding.NewEncoder(file, &cfg) exitOnErr(cmd, err, true) @@ -93,13 +104,42 @@ func newFmtCmd(c *Command) *cobra.Command { err := e.EncodeFile(f) exitOnErr(cmd, err, false) } + + if check { + var originalBytes []byte + if file.Filename != "-" { + originalBytes, err = os.ReadFile(file.Filename) + exitOnErr(cmd, err, true) + } else { + originalBytes = file.Source.([]byte) + } + + if !bytes.Equal(formatted.Bytes(), originalBytes) { + modifiedFiles = append(modifiedFiles, file.Filename) + } + } + if err := e.Close(); err != nil { exitOnErr(cmd, err, true) } } } + + if check && len(modifiedFiles) > 0 { + stdout := cmd.OutOrStdout() + for _, f := range modifiedFiles { + if f != "-" { + fmt.Fprintln(stdout, f) + } + } + os.Exit(1) + } + return nil }), } + + cmd.Flags().Bool(string(flagCheck), false, "exits with non-zero status if any files are not formatted") + return cmd } diff --git a/cmd/cue/cmd/testdata/script/fmt_check.txtar b/cmd/cue/cmd/testdata/script/fmt_check.txtar new file mode 100644 index 00000000000..3267e53edeb --- /dev/null +++ b/cmd/cue/cmd/testdata/script/fmt_check.txtar @@ -0,0 +1,15 @@ +# succeeds when file is formatted +exec cue fmt --check formatted.cue + +# files are not modified +exec stat -f %c formatted.cue +cp stdout formatted_time_before +exec cue fmt formatted.cue +exec stat -f %c formatted.cue +cmp stdout formatted_time_before + +stdin formatted.cue +exec cue fmt --check - + +-- formatted.cue -- +foo: "bar" \ No newline at end of file diff --git a/cmd/cue/cmd/testdata/script/fmt_check_fail.txtar b/cmd/cue/cmd/testdata/script/fmt_check_fail.txtar new file mode 100644 index 00000000000..cfc9f59ea02 --- /dev/null +++ b/cmd/cue/cmd/testdata/script/fmt_check_fail.txtar @@ -0,0 +1,20 @@ +# fails and displays non formatted files +! exec cue fmt --check not_formatted.cue another_not_formatted.cue +cp stdout actual-stdout +grep not_formatted.cue actual-stdout +grep another_not_formatted.cue actual-stdout +! grep correct.cue actual-stdout + +# stdin fails with no output +stdin not_formatted.cue +! exec cue fmt --check - +cmp stdout expect-empty-stdout + +-- correct.cue -- +foo: "bar" +-- not_formatted.cue -- +foo: "bar" +-- another_not_formatted.cue -- +bar: "baz" +x: 1 +-- expect-empty-stdout -- \ No newline at end of file diff --git a/cue/ast/ast.go b/cue/ast/ast.go index 5b9a3b544dc..a55b902149c 100644 --- a/cue/ast/ast.go +++ b/cue/ast/ast.go @@ -951,7 +951,7 @@ func (d *EmbedDecl) End() token.Pos { return d.Expr.End() } // ---------------------------------------------------------------------------- // Files and packages -// A File node represents a Go source file. +// A File node represents a Cue source file. // // The Comments list contains all comments in the source file in order of // appearance, including the comments that are pointed to from other nodes