Skip to content

Commit 5a783ba

Browse files
authored
feat: new fmt command with dedicated formatter configuration (#5357)
1 parent 0b4dee5 commit 5a783ba

24 files changed

+952
-404
lines changed

.github/workflows/pr.yml

+3
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ jobs:
128128

129129
- run: ./golangci-lint
130130

131+
- run: ./golangci-lint fmt
132+
- run: ./golangci-lint fmt --diff
133+
131134
- run: ./golangci-lint cache
132135
- run: ./golangci-lint cache status
133136
- run: ./golangci-lint cache clean

cmd/golangci-lint/main.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,10 @@ func createBuildInfo() commands.BuildInfo {
6464
}
6565
}
6666

67-
revision = cmp.Or(revision, "unknown")
68-
modified = cmp.Or(modified, "?")
6967
info.Date = cmp.Or(info.Date, "(unknown)")
7068

71-
info.Commit = fmt.Sprintf("(%s, modified: %s, mod sum: %q)", revision, modified, buildInfo.Main.Sum)
69+
info.Commit = fmt.Sprintf("(%s, modified: %s, mod sum: %q)",
70+
cmp.Or(revision, "unknown"), cmp.Or(modified, "?"), buildInfo.Main.Sum)
7271

7372
return info
7473
}

pkg/commands/flagsets.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ func setupLintersFlagSet(v *viper.Viper, fs *pflag.FlagSet) {
3838
color.GreenString("Override linters configuration section to only run the specific linter(s)")) // Flags only.
3939
}
4040

41+
func setupFormattersFlagSet(v *viper.Viper, fs *pflag.FlagSet) {
42+
internal.AddFlagAndBindP(v, fs, fs.StringSliceP, "enable", "E", "formatters.enable", nil,
43+
color.GreenString("Enable specific formatter"))
44+
}
45+
4146
func setupRunFlagSet(v *viper.Viper, fs *pflag.FlagSet) {
4247
internal.AddFlagAndBindP(v, fs, fs.IntP, "concurrency", "j", "run.concurrency", getDefaultConcurrency(),
4348
color.GreenString("Number of CPUs to use (Default: number of logical CPUs)"))
@@ -102,7 +107,7 @@ func setupIssuesFlagSet(v *viper.Viper, fs *pflag.FlagSet) {
102107
internal.AddFlagAndBind(v, fs, fs.Bool, "exclude-dirs-use-default", "issues.exclude-dirs-use-default", true,
103108
formatList("Use or not use default excluded directories:", processors.StdExcludeDirRegexps))
104109

105-
internal.AddFlagAndBind(v, fs, fs.String, "exclude-generated", "issues.exclude-generated", processors.AutogeneratedModeLax,
110+
internal.AddFlagAndBind(v, fs, fs.String, "exclude-generated", "issues.exclude-generated", config.GeneratedModeLax,
106111
color.GreenString("Mode of the generated files analysis"))
107112

108113
const newDesc = "Show only new issues: if there are unstaged changes or untracked files, only those changes " +

pkg/commands/fmt.go

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package commands
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
9+
"github.com/fatih/color"
10+
"github.com/spf13/cobra"
11+
"github.com/spf13/viper"
12+
13+
"github.com/golangci/golangci-lint/pkg/config"
14+
"github.com/golangci/golangci-lint/pkg/goformat"
15+
"github.com/golangci/golangci-lint/pkg/goformatters"
16+
"github.com/golangci/golangci-lint/pkg/logutils"
17+
"github.com/golangci/golangci-lint/pkg/result/processors"
18+
)
19+
20+
type fmtOptions struct {
21+
config.LoaderOptions
22+
23+
diff bool // Flag only.
24+
}
25+
26+
type fmtCommand struct {
27+
viper *viper.Viper
28+
cmd *cobra.Command
29+
30+
opts fmtOptions
31+
32+
cfg *config.Config
33+
34+
buildInfo BuildInfo
35+
36+
runner *goformat.Runner
37+
38+
log logutils.Log
39+
debugf logutils.DebugFunc
40+
}
41+
42+
func newFmtCommand(logger logutils.Log, info BuildInfo) *fmtCommand {
43+
c := &fmtCommand{
44+
viper: viper.New(),
45+
log: logger,
46+
debugf: logutils.Debug(logutils.DebugKeyExec),
47+
cfg: config.NewDefault(),
48+
buildInfo: info,
49+
}
50+
51+
fmtCmd := &cobra.Command{
52+
Use: "fmt",
53+
Short: "Format Go source files",
54+
RunE: c.execute,
55+
PreRunE: c.preRunE,
56+
PersistentPreRunE: c.persistentPreRunE,
57+
PersistentPostRun: c.persistentPostRun,
58+
SilenceUsage: true,
59+
}
60+
61+
fmtCmd.SetOut(logutils.StdOut) // use custom output to properly color it in Windows terminals
62+
fmtCmd.SetErr(logutils.StdErr)
63+
64+
fs := fmtCmd.Flags()
65+
fs.SortFlags = false // sort them as they are defined here
66+
67+
setupConfigFileFlagSet(fs, &c.opts.LoaderOptions)
68+
69+
setupFormattersFlagSet(c.viper, fs)
70+
71+
fs.BoolVarP(&c.opts.diff, "diff", "d", false, color.GreenString("Display diffs instead of rewriting files"))
72+
73+
c.cmd = fmtCmd
74+
75+
return c
76+
}
77+
78+
func (c *fmtCommand) persistentPreRunE(cmd *cobra.Command, args []string) error {
79+
c.log.Infof("%s", c.buildInfo.String())
80+
81+
loader := config.NewLoader(c.log.Child(logutils.DebugKeyConfigReader), c.viper, cmd.Flags(), c.opts.LoaderOptions, c.cfg, args)
82+
83+
err := loader.Load(config.LoadOptions{CheckDeprecation: true, Validation: true})
84+
if err != nil {
85+
return fmt.Errorf("can't load config: %w", err)
86+
}
87+
88+
return nil
89+
}
90+
91+
func (c *fmtCommand) preRunE(_ *cobra.Command, _ []string) error {
92+
metaFormatter, err := goformatters.NewMetaFormatter(c.log, &c.cfg.Formatters, &c.cfg.Run)
93+
if err != nil {
94+
return fmt.Errorf("failed to create meta-formatter: %w", err)
95+
}
96+
97+
matcher := processors.NewGeneratedFileMatcher(c.cfg.Formatters.Exclusions.Generated)
98+
99+
opts, err := goformat.NewRunnerOptions(c.cfg, c.opts.diff)
100+
if err != nil {
101+
return fmt.Errorf("build walk options: %w", err)
102+
}
103+
104+
c.runner = goformat.NewRunner(c.log, metaFormatter, matcher, opts)
105+
106+
return nil
107+
}
108+
109+
func (c *fmtCommand) execute(_ *cobra.Command, args []string) error {
110+
paths, err := cleanArgs(args)
111+
if err != nil {
112+
return fmt.Errorf("failed to clean arguments: %w", err)
113+
}
114+
115+
c.log.Infof("Formatting Go files...")
116+
117+
err = c.runner.Run(paths)
118+
if err != nil {
119+
return fmt.Errorf("failed to process files: %w", err)
120+
}
121+
122+
return nil
123+
}
124+
125+
func (c *fmtCommand) persistentPostRun(_ *cobra.Command, _ []string) {
126+
if c.runner.ExitCode() != 0 {
127+
os.Exit(c.runner.ExitCode())
128+
}
129+
}
130+
131+
func cleanArgs(args []string) ([]string, error) {
132+
if len(args) == 0 {
133+
abs, err := filepath.Abs(".")
134+
if err != nil {
135+
return nil, err
136+
}
137+
138+
return []string{abs}, nil
139+
}
140+
141+
var expanded []string
142+
for _, arg := range args {
143+
abs, err := filepath.Abs(strings.ReplaceAll(arg, "...", ""))
144+
if err != nil {
145+
return nil, err
146+
}
147+
148+
expanded = append(expanded, abs)
149+
}
150+
151+
return expanded, nil
152+
}

pkg/commands/root.go

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func newRootCommand(info BuildInfo) *rootCommand {
6060
rootCmd.AddCommand(
6161
newLintersCommand(log).cmd,
6262
newRunCommand(log, info).cmd,
63+
newFmtCommand(log, info).cmd,
6364
newCacheCommand().cmd,
6465
newConfigCommand(log, info).cmd,
6566
newVersionCommand(info).cmd,

pkg/config/config.go

+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ type Config struct {
2929
Issues Issues `mapstructure:"issues"`
3030
Severity Severity `mapstructure:"severity"`
3131

32+
Formatters Formatters `mapstructure:"formatters"`
33+
3234
InternalCmdTest bool // Option is used only for testing golangci-lint command, don't use it
3335
InternalTest bool // Option is used only for testing golangci-lint code, don't use it
3436
}
@@ -64,6 +66,9 @@ func (c *Config) Validate() error {
6466
func NewDefault() *Config {
6567
return &Config{
6668
LintersSettings: defaultLintersSettings,
69+
Formatters: Formatters{
70+
Settings: defaultFormatterSettings,
71+
},
6772
}
6873
}
6974

pkg/config/formatters.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package config
2+
3+
type Formatters struct {
4+
Enable []string `mapstructure:"enable"`
5+
Settings FormatterSettings `mapstructure:"settings"`
6+
Exclusions FormatterExclusions `mapstructure:"exclusions"`
7+
}
8+
9+
type FormatterExclusions struct {
10+
Generated string `mapstructure:"generated"`
11+
Paths []string `mapstructure:"paths"`
12+
}

pkg/config/formatters_settings.go

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package config
2+
3+
var defaultFormatterSettings = FormatterSettings{
4+
GoFmt: GoFmtSettings{
5+
Simplify: true,
6+
},
7+
Gci: GciSettings{
8+
Sections: []string{"standard", "default"},
9+
SkipGenerated: true,
10+
},
11+
}
12+
13+
type FormatterSettings struct {
14+
Gci GciSettings `mapstructure:"gci"`
15+
GoFmt GoFmtSettings `mapstructure:"gofmt"`
16+
GoFumpt GoFumptSettings `mapstructure:"gofumpt"`
17+
GoImports GoImportsSettings `mapstructure:"goimports"`
18+
}
19+
20+
type GciSettings struct {
21+
Sections []string `mapstructure:"sections"`
22+
NoInlineComments bool `mapstructure:"no-inline-comments"`
23+
NoPrefixComments bool `mapstructure:"no-prefix-comments"`
24+
SkipGenerated bool `mapstructure:"skip-generated"`
25+
CustomOrder bool `mapstructure:"custom-order"`
26+
NoLexOrder bool `mapstructure:"no-lex-order"`
27+
28+
// Deprecated: use Sections instead.
29+
LocalPrefixes string `mapstructure:"local-prefixes"`
30+
}
31+
32+
type GoFmtSettings struct {
33+
Simplify bool
34+
RewriteRules []GoFmtRewriteRule `mapstructure:"rewrite-rules"`
35+
}
36+
37+
type GoFmtRewriteRule struct {
38+
Pattern string
39+
Replacement string
40+
}
41+
42+
type GoFumptSettings struct {
43+
ModulePath string `mapstructure:"module-path"`
44+
ExtraRules bool `mapstructure:"extra-rules"`
45+
46+
// Deprecated: use the global `run.go` instead.
47+
LangVersion string `mapstructure:"lang-version"`
48+
}
49+
50+
type GoImportsSettings struct {
51+
LocalPrefixes string `mapstructure:"local-prefixes"`
52+
}

pkg/config/linters_exclusions.go

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import (
55
"slices"
66
)
77

8+
const (
9+
GeneratedModeLax = "lax"
10+
GeneratedModeStrict = "strict"
11+
GeneratedModeDisable = "disable"
12+
)
13+
814
const (
915
ExclusionPresetComments = "comments"
1016
ExclusionPresetStdErrorHandling = "stdErrorHandling"

0 commit comments

Comments
 (0)