Skip to content

Commit 1d8c9c1

Browse files
committed
feat: add diff flag (with exit code)
1 parent e6ae000 commit 1d8c9c1

File tree

2 files changed

+69
-30
lines changed

2 files changed

+69
-30
lines changed

pkg/commands/fmt.go

+20-27
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ package commands
22

33
import (
44
"fmt"
5-
"io"
6-
"log"
75
"os"
86
"path/filepath"
97
"strings"
108

9+
"github.com/fatih/color"
1110
"github.com/spf13/cobra"
1211
"github.com/spf13/viper"
1312

@@ -18,11 +17,17 @@ import (
1817
"github.com/golangci/golangci-lint/pkg/result/processors"
1918
)
2019

20+
type fmtOptions struct {
21+
config.LoaderOptions
22+
23+
diff bool // Flag only.
24+
}
25+
2126
type fmtCommand struct {
2227
viper *viper.Viper
2328
cmd *cobra.Command
2429

25-
opts config.LoaderOptions
30+
opts fmtOptions
2631

2732
cfg *config.Config
2833

@@ -49,18 +54,21 @@ func newFmtCommand(logger logutils.Log, info BuildInfo) *fmtCommand {
4954
RunE: c.execute,
5055
PreRunE: c.preRunE,
5156
PersistentPreRunE: c.persistentPreRunE,
57+
PersistentPostRun: c.persistentPostRun,
5258
SilenceUsage: true,
5359
}
5460

5561
fmtCmd.SetOut(logutils.StdOut) // use custom output to properly color it in Windows terminals
5662
fmtCmd.SetErr(logutils.StdErr)
5763

58-
flagSet := fmtCmd.Flags()
59-
flagSet.SortFlags = false // sort them as they are defined here
64+
fs := fmtCmd.Flags()
65+
fs.SortFlags = false // sort them as they are defined here
66+
67+
setupConfigFileFlagSet(fs, &c.opts.LoaderOptions)
6068

61-
setupConfigFileFlagSet(flagSet, &c.opts)
69+
setupFormattersFlagSet(c.viper, fs)
6270

63-
setupFormattersFlagSet(c.viper, flagSet)
71+
fs.BoolVarP(&c.opts.diff, "diff", "d", false, color.GreenString("Display diffs instead of rewriting files"))
6472

6573
c.cmd = fmtCmd
6674

@@ -70,7 +78,7 @@ func newFmtCommand(logger logutils.Log, info BuildInfo) *fmtCommand {
7078
func (c *fmtCommand) persistentPreRunE(cmd *cobra.Command, args []string) error {
7179
c.log.Infof("%s", c.buildInfo.String())
7280

73-
loader := config.NewLoader(c.log.Child(logutils.DebugKeyConfigReader), c.viper, cmd.Flags(), c.opts, c.cfg, args)
81+
loader := config.NewLoader(c.log.Child(logutils.DebugKeyConfigReader), c.viper, cmd.Flags(), c.opts.LoaderOptions, c.cfg, args)
7482

7583
err := loader.Load(config.LoadOptions{CheckDeprecation: true, Validation: true})
7684
if err != nil {
@@ -88,7 +96,7 @@ func (c *fmtCommand) preRunE(_ *cobra.Command, _ []string) error {
8896

8997
matcher := processors.NewGeneratedFileMatcher(c.cfg.Formatters.Exclusions.Generated)
9098

91-
opts, err := goformat.NewRunnerOptions(c.cfg)
99+
opts, err := goformat.NewRunnerOptions(c.cfg, c.opts.diff)
92100
if err != nil {
93101
return fmt.Errorf("build walk options: %w", err)
94102
}
@@ -99,15 +107,6 @@ func (c *fmtCommand) preRunE(_ *cobra.Command, _ []string) error {
99107
}
100108

101109
func (c *fmtCommand) execute(_ *cobra.Command, args []string) error {
102-
if !logutils.HaveDebugTag(logutils.DebugKeyFormattersOutput) {
103-
// Don't allow linters and loader to print anything
104-
log.SetOutput(io.Discard)
105-
savedStdout, savedStderr := c.setOutputToDevNull()
106-
defer func() {
107-
os.Stdout, os.Stderr = savedStdout, savedStderr
108-
}()
109-
}
110-
111110
paths, err := cleanArgs(args)
112111
if err != nil {
113112
return fmt.Errorf("failed to clean arguments: %w", err)
@@ -123,16 +122,10 @@ func (c *fmtCommand) execute(_ *cobra.Command, args []string) error {
123122
return nil
124123
}
125124

126-
func (c *fmtCommand) setOutputToDevNull() (savedStdout, savedStderr *os.File) {
127-
savedStdout, savedStderr = os.Stdout, os.Stderr
128-
devNull, err := os.Open(os.DevNull)
129-
if err != nil {
130-
c.log.Warnf("Can't open null device %q: %s", os.DevNull, err)
131-
return
125+
func (c *fmtCommand) persistentPostRun(_ *cobra.Command, _ []string) {
126+
if c.runner.ExitCode() != 0 {
127+
os.Exit(c.runner.ExitCode())
132128
}
133-
134-
os.Stdout, os.Stderr = devNull, devNull
135-
return
136129
}
137130

138131
func cleanArgs(args []string) ([]string, error) {

pkg/goformat/runner.go

+49-3
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ import (
44
"bytes"
55
"context"
66
"fmt"
7+
"io"
78
"io/fs"
9+
"log"
810
"os"
911
"path/filepath"
1012
"regexp"
1113
"strings"
1214

15+
"github.com/rogpeppe/go-internal/diff"
16+
1317
"github.com/golangci/golangci-lint/pkg/config"
1418
"github.com/golangci/golangci-lint/pkg/fsutils"
1519
"github.com/golangci/golangci-lint/pkg/goformatters"
@@ -24,6 +28,8 @@ type Runner struct {
2428
matcher *processors.GeneratedFileMatcher
2529

2630
opts RunnerOptions
31+
32+
exitCode int
2733
}
2834

2935
func NewRunner(log logutils.Log,
@@ -38,8 +44,19 @@ func NewRunner(log logutils.Log,
3844
}
3945

4046
func (c *Runner) Run(paths []string) error {
47+
savedStdout, savedStderr := os.Stdout, os.Stderr
48+
49+
if !logutils.HaveDebugTag(logutils.DebugKeyFormattersOutput) {
50+
// Don't allow linters and loader to print anything
51+
log.SetOutput(io.Discard)
52+
c.setOutputToDevNull()
53+
defer func() {
54+
os.Stdout, os.Stderr = savedStdout, savedStderr
55+
}()
56+
}
57+
4158
for _, path := range paths {
42-
err := c.walk(path)
59+
err := c.walk(path, savedStdout)
4360
if err != nil {
4461
return err
4562
}
@@ -48,7 +65,7 @@ func (c *Runner) Run(paths []string) error {
4865
return nil
4966
}
5067

51-
func (c *Runner) walk(root string) error {
68+
func (c *Runner) walk(root string, stdout *os.File) error {
5269
return filepath.Walk(root, func(path string, f fs.FileInfo, err error) error {
5370
if err != nil {
5471
return err
@@ -83,6 +100,19 @@ func (c *Runner) walk(root string) error {
83100
return nil
84101
}
85102

103+
if c.opts.diff {
104+
newName := filepath.ToSlash(path)
105+
oldName := newName + ".orig"
106+
_, err = stdout.Write(diff.Diff(oldName, input, newName, output))
107+
if err != nil {
108+
return err
109+
}
110+
111+
c.exitCode = 1
112+
113+
return nil
114+
}
115+
86116
c.log.Infof("format: %s", path)
87117

88118
// On Windows, we need to re-set the permissions from the file. See golang/go#38225.
@@ -95,13 +125,28 @@ func (c *Runner) walk(root string) error {
95125
})
96126
}
97127

128+
func (c *Runner) setOutputToDevNull() {
129+
devNull, err := os.Open(os.DevNull)
130+
if err != nil {
131+
c.log.Warnf("Can't open null device %q: %s", os.DevNull, err)
132+
return
133+
}
134+
135+
os.Stdout, os.Stderr = devNull, devNull
136+
}
137+
138+
func (c *Runner) ExitCode() int {
139+
return c.exitCode
140+
}
141+
98142
type RunnerOptions struct {
99143
basePath string
100144
patterns []*regexp.Regexp
101145
generated string
146+
diff bool
102147
}
103148

104-
func NewRunnerOptions(cfg *config.Config) (RunnerOptions, error) {
149+
func NewRunnerOptions(cfg *config.Config, diff bool) (RunnerOptions, error) {
105150
basePath, err := fsutils.GetBasePath(context.Background(), cfg.Run.RelativePathMode, cfg.GetConfigDir())
106151
if err != nil {
107152
return RunnerOptions{}, fmt.Errorf("get base path: %w", err)
@@ -110,6 +155,7 @@ func NewRunnerOptions(cfg *config.Config) (RunnerOptions, error) {
110155
opts := RunnerOptions{
111156
basePath: basePath,
112157
generated: cfg.Formatters.Exclusions.Generated,
158+
diff: diff,
113159
}
114160

115161
for _, pattern := range cfg.Formatters.Exclusions.Paths {

0 commit comments

Comments
 (0)