Skip to content

Commit 11d102b

Browse files
authored
Merge pull request #453 from numtide/fix/exit-with-error-formatters-fail
fix: exit with error if any formatters fail
2 parents c8a96da + 39774dc commit 11d102b

File tree

3 files changed

+40
-25
lines changed

3 files changed

+40
-25
lines changed

cmd/format/format.go

+18-7
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ const (
3232
BatchSize = 1024
3333
)
3434

35-
var ErrFailOnChange = errors.New("unexpected changes detected, --fail-on-change is enabled")
35+
var (
36+
ErrFailOnChange = errors.New("unexpected changes detected, --fail-on-change is enabled")
37+
ErrFormattingFailures = errors.New("formatting failures detected")
38+
)
3639

3740
func Run(v *viper.Viper, statz *stats.Stats, cmd *cobra.Command, paths []string) error {
3841
cmd.SilenceUsage = true
@@ -404,6 +407,8 @@ func postProcessing(
404407
formattedCh chan *format.Task,
405408
) func() error {
406409
return func() error {
410+
var formattingFailures bool // track if there were any formatting failures
411+
407412
LOOP:
408413
for {
409414
select {
@@ -420,8 +425,9 @@ func postProcessing(
420425
// grab the underlying file reference
421426
file := task.File
422427

423-
// check if there were any errors processing the file
424428
if len(task.Errors) > 0 {
429+
formattingFailures = true
430+
425431
// release the file, passing the first task error
426432
// note: task errors are related to the batch in which a task was applied
427433
// this does not necessarily indicate this file had a problem being formatted, but this approach
@@ -471,16 +477,21 @@ func postProcessing(
471477
}
472478
}
473479

474-
// if fail on change has been enabled, check that no files were actually changed, throwing an error if so
475-
if cfg.FailOnChange && statz.Value(stats.Changed) != 0 {
476-
return ErrFailOnChange
477-
}
478-
479480
// print stats to stdout unless we are processing stdin and printing the results to stdout
480481
if !cfg.Stdin {
481482
statz.Print()
482483
}
483484

485+
// return an error if any formatting failures were detected
486+
if formattingFailures {
487+
return ErrFormattingFailures
488+
}
489+
490+
// if fail on change has been enabled, check that no files were actually changed, throwing an error if so
491+
if cfg.FailOnChange && statz.Value(stats.Changed) != 0 {
492+
return ErrFailOnChange
493+
}
494+
484495
return nil
485496
}
486497
}

cmd/root_test.go

+21-17
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ import (
99
"path"
1010
"path/filepath"
1111
"regexp"
12+
"strings"
1213
"testing"
1314
"time"
1415

1516
"github.com/charmbracelet/log"
1617
"github.com/numtide/treefmt/cmd"
17-
format2 "github.com/numtide/treefmt/cmd/format"
18+
formatCmd "github.com/numtide/treefmt/cmd/format"
1819
"github.com/numtide/treefmt/config"
1920
"github.com/numtide/treefmt/format"
2021
"github.com/numtide/treefmt/stats"
@@ -482,7 +483,7 @@ func TestCache(t *testing.T) {
482483

483484
// running should match but not format anything
484485
_, statz, err = treefmt(t, "--config-file", configPath, "--tree-root", tempDir)
485-
as.NoError(err)
486+
as.ErrorIs(err, formatCmd.ErrFormattingFailures)
486487

487488
assertStats(t, as, statz, map[stats.Type]int{
488489
stats.Traversed: 32,
@@ -492,8 +493,8 @@ func TestCache(t *testing.T) {
492493
})
493494

494495
// running again should provide the same result
495-
_, statz, err = treefmt(t, "--config-file", configPath, "--tree-root", tempDir, "-vv")
496-
as.NoError(err)
496+
_, statz, err = treefmt(t, "--config-file", configPath, "--tree-root", tempDir)
497+
as.ErrorIs(err, formatCmd.ErrFormattingFailures)
497498

498499
assertStats(t, as, statz, map[stats.Type]int{
499500
stats.Traversed: 32,
@@ -588,13 +589,13 @@ func TestFailOnChange(t *testing.T) {
588589

589590
test.WriteConfig(t, configPath, cfg)
590591
_, _, err := treefmt(t, "--fail-on-change", "--config-file", configPath, "--tree-root", tempDir)
591-
as.ErrorIs(err, format2.ErrFailOnChange)
592+
as.ErrorIs(err, formatCmd.ErrFailOnChange)
592593

593594
// test with no cache
594595
t.Setenv("TREEFMT_FAIL_ON_CHANGE", "true")
595596
test.WriteConfig(t, configPath, cfg)
596597
_, _, err = treefmt(t, "--config-file", configPath, "--tree-root", tempDir, "--no-cache")
597-
as.ErrorIs(err, format2.ErrFailOnChange)
598+
as.ErrorIs(err, formatCmd.ErrFailOnChange)
598599
}
599600

600601
func TestBustCacheOnFormatterChange(t *testing.T) {
@@ -1027,7 +1028,7 @@ func TestStdin(t *testing.T) {
10271028
// we get an error about the missing filename parameter.
10281029
out, _, err := treefmt(t, "-C", tempDir, "--allow-missing-formatter", "--stdin")
10291030
as.EqualError(err, "exactly one path should be specified when using the --stdin flag")
1030-
as.Equal("", string(out))
1031+
as.Equal("Error: exactly one path should be specified when using the --stdin flag\n", string(out))
10311032

10321033
// now pass along the filename parameter
10331034
os.Stdin = test.TempFile(t, "", "stdin", &contents)
@@ -1051,7 +1052,7 @@ func TestStdin(t *testing.T) {
10511052

10521053
out, _, err = treefmt(t, "-C", tempDir, "--allow-missing-formatter", "--stdin", "../test.nix")
10531054
as.Errorf(err, "path ../test.nix not inside the tree root %s", tempDir)
1054-
as.Equal("", string(out))
1055+
as.Contains(string(out), "Error: path ../test.nix not inside the tree root")
10551056

10561057
// try some markdown instead
10571058
contents = `
@@ -1239,6 +1240,8 @@ func TestRunInSubdir(t *testing.T) {
12391240
func treefmt(t *testing.T, args ...string) ([]byte, *stats.Stats, error) {
12401241
t.Helper()
12411242

1243+
t.Logf("treefmt %s", strings.Join(args, " "))
1244+
12421245
tempDir := t.TempDir()
12431246
tempOut := test.TempFile(t, tempDir, "combined_output", nil)
12441247

@@ -1281,21 +1284,22 @@ func treefmt(t *testing.T, args ...string) ([]byte, *stats.Stats, error) {
12811284
time.Sleep(time.Until(waitUntil))
12821285
}()
12831286

1284-
if err := root.Execute(); err != nil {
1285-
return nil, nil, err
1286-
}
1287+
// execute the command
1288+
cmdErr := root.Execute()
12871289

12881290
// reset and read the temporary output
1289-
if _, err := tempOut.Seek(0, 0); err != nil {
1290-
return nil, nil, fmt.Errorf("failed to reset temp output for reading: %w", err)
1291+
if _, resetErr := tempOut.Seek(0, 0); resetErr != nil {
1292+
t.Fatal(fmt.Errorf("failed to reset temp output for reading: %w", resetErr))
12911293
}
12921294

1293-
out, err := io.ReadAll(tempOut)
1294-
if err != nil {
1295-
return nil, nil, fmt.Errorf("failed to read temp output: %w", err)
1295+
out, readErr := io.ReadAll(tempOut)
1296+
if readErr != nil {
1297+
t.Fatal(fmt.Errorf("failed to read temp output: %w", readErr))
12961298
}
12971299

1298-
return out, statz, nil
1300+
t.Log(string(out))
1301+
1302+
return out, statz, cmdErr
12991303
}
13001304

13011305
func assertStats(

format/formatter.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func (f *Formatter) Apply(ctx context.Context, tasks []*Task) error {
7777
f.log.Errorf("failed to apply with options '%v': %s", f.config.Options, err)
7878

7979
if len(out) > 0 {
80-
_, _ = fmt.Fprintf(os.Stderr, "%s error:\n%s\n", f.name, out)
80+
_, _ = fmt.Fprintf(os.Stderr, "\n%s\n", out)
8181
}
8282

8383
return fmt.Errorf("formatter '%s' with options '%v' failed to apply: %w", f.config.Command, f.config.Options, err)

0 commit comments

Comments
 (0)