Skip to content

Commit

Permalink
feat: new fmt command with dedicated formatter configuration (#5357)
Browse files Browse the repository at this point in the history
  • Loading branch information
ldez authored Feb 17, 2025
1 parent 0b4dee5 commit 5a783ba
Show file tree
Hide file tree
Showing 24 changed files with 952 additions and 404 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ jobs:

- run: ./golangci-lint

- run: ./golangci-lint fmt
- run: ./golangci-lint fmt --diff

- run: ./golangci-lint cache
- run: ./golangci-lint cache status
- run: ./golangci-lint cache clean
Expand Down
5 changes: 2 additions & 3 deletions cmd/golangci-lint/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,10 @@ func createBuildInfo() commands.BuildInfo {
}
}

revision = cmp.Or(revision, "unknown")
modified = cmp.Or(modified, "?")
info.Date = cmp.Or(info.Date, "(unknown)")

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

return info
}
7 changes: 6 additions & 1 deletion pkg/commands/flagsets.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ func setupLintersFlagSet(v *viper.Viper, fs *pflag.FlagSet) {
color.GreenString("Override linters configuration section to only run the specific linter(s)")) // Flags only.
}

func setupFormattersFlagSet(v *viper.Viper, fs *pflag.FlagSet) {
internal.AddFlagAndBindP(v, fs, fs.StringSliceP, "enable", "E", "formatters.enable", nil,
color.GreenString("Enable specific formatter"))
}

func setupRunFlagSet(v *viper.Viper, fs *pflag.FlagSet) {
internal.AddFlagAndBindP(v, fs, fs.IntP, "concurrency", "j", "run.concurrency", getDefaultConcurrency(),
color.GreenString("Number of CPUs to use (Default: number of logical CPUs)"))
Expand Down Expand Up @@ -102,7 +107,7 @@ func setupIssuesFlagSet(v *viper.Viper, fs *pflag.FlagSet) {
internal.AddFlagAndBind(v, fs, fs.Bool, "exclude-dirs-use-default", "issues.exclude-dirs-use-default", true,
formatList("Use or not use default excluded directories:", processors.StdExcludeDirRegexps))

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

const newDesc = "Show only new issues: if there are unstaged changes or untracked files, only those changes " +
Expand Down
152 changes: 152 additions & 0 deletions pkg/commands/fmt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package commands

import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/golangci/golangci-lint/pkg/config"
"github.com/golangci/golangci-lint/pkg/goformat"
"github.com/golangci/golangci-lint/pkg/goformatters"
"github.com/golangci/golangci-lint/pkg/logutils"
"github.com/golangci/golangci-lint/pkg/result/processors"
)

type fmtOptions struct {
config.LoaderOptions

diff bool // Flag only.
}

type fmtCommand struct {
viper *viper.Viper
cmd *cobra.Command

opts fmtOptions

cfg *config.Config

buildInfo BuildInfo

runner *goformat.Runner

log logutils.Log
debugf logutils.DebugFunc
}

func newFmtCommand(logger logutils.Log, info BuildInfo) *fmtCommand {
c := &fmtCommand{
viper: viper.New(),
log: logger,
debugf: logutils.Debug(logutils.DebugKeyExec),
cfg: config.NewDefault(),
buildInfo: info,
}

fmtCmd := &cobra.Command{
Use: "fmt",
Short: "Format Go source files",
RunE: c.execute,
PreRunE: c.preRunE,
PersistentPreRunE: c.persistentPreRunE,
PersistentPostRun: c.persistentPostRun,
SilenceUsage: true,
}

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

fs := fmtCmd.Flags()
fs.SortFlags = false // sort them as they are defined here

setupConfigFileFlagSet(fs, &c.opts.LoaderOptions)

setupFormattersFlagSet(c.viper, fs)

fs.BoolVarP(&c.opts.diff, "diff", "d", false, color.GreenString("Display diffs instead of rewriting files"))

c.cmd = fmtCmd

return c
}

func (c *fmtCommand) persistentPreRunE(cmd *cobra.Command, args []string) error {
c.log.Infof("%s", c.buildInfo.String())

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

err := loader.Load(config.LoadOptions{CheckDeprecation: true, Validation: true})
if err != nil {
return fmt.Errorf("can't load config: %w", err)
}

return nil
}

func (c *fmtCommand) preRunE(_ *cobra.Command, _ []string) error {
metaFormatter, err := goformatters.NewMetaFormatter(c.log, &c.cfg.Formatters, &c.cfg.Run)
if err != nil {
return fmt.Errorf("failed to create meta-formatter: %w", err)
}

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

opts, err := goformat.NewRunnerOptions(c.cfg, c.opts.diff)
if err != nil {
return fmt.Errorf("build walk options: %w", err)
}

c.runner = goformat.NewRunner(c.log, metaFormatter, matcher, opts)

return nil
}

func (c *fmtCommand) execute(_ *cobra.Command, args []string) error {
paths, err := cleanArgs(args)
if err != nil {
return fmt.Errorf("failed to clean arguments: %w", err)
}

c.log.Infof("Formatting Go files...")

err = c.runner.Run(paths)
if err != nil {
return fmt.Errorf("failed to process files: %w", err)
}

return nil
}

func (c *fmtCommand) persistentPostRun(_ *cobra.Command, _ []string) {
if c.runner.ExitCode() != 0 {
os.Exit(c.runner.ExitCode())
}
}

func cleanArgs(args []string) ([]string, error) {
if len(args) == 0 {
abs, err := filepath.Abs(".")
if err != nil {
return nil, err
}

return []string{abs}, nil
}

var expanded []string
for _, arg := range args {
abs, err := filepath.Abs(strings.ReplaceAll(arg, "...", ""))
if err != nil {
return nil, err
}

expanded = append(expanded, abs)
}

return expanded, nil
}
1 change: 1 addition & 0 deletions pkg/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func newRootCommand(info BuildInfo) *rootCommand {
rootCmd.AddCommand(
newLintersCommand(log).cmd,
newRunCommand(log, info).cmd,
newFmtCommand(log, info).cmd,
newCacheCommand().cmd,
newConfigCommand(log, info).cmd,
newVersionCommand(info).cmd,
Expand Down
5 changes: 5 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ type Config struct {
Issues Issues `mapstructure:"issues"`
Severity Severity `mapstructure:"severity"`

Formatters Formatters `mapstructure:"formatters"`

InternalCmdTest bool // Option is used only for testing golangci-lint command, don't use it
InternalTest bool // Option is used only for testing golangci-lint code, don't use it
}
Expand Down Expand Up @@ -64,6 +66,9 @@ func (c *Config) Validate() error {
func NewDefault() *Config {
return &Config{
LintersSettings: defaultLintersSettings,
Formatters: Formatters{
Settings: defaultFormatterSettings,
},
}
}

Expand Down
12 changes: 12 additions & 0 deletions pkg/config/formatters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package config

type Formatters struct {
Enable []string `mapstructure:"enable"`
Settings FormatterSettings `mapstructure:"settings"`
Exclusions FormatterExclusions `mapstructure:"exclusions"`
}

type FormatterExclusions struct {
Generated string `mapstructure:"generated"`
Paths []string `mapstructure:"paths"`
}
52 changes: 52 additions & 0 deletions pkg/config/formatters_settings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package config

var defaultFormatterSettings = FormatterSettings{
GoFmt: GoFmtSettings{
Simplify: true,
},
Gci: GciSettings{
Sections: []string{"standard", "default"},
SkipGenerated: true,
},
}

type FormatterSettings struct {
Gci GciSettings `mapstructure:"gci"`
GoFmt GoFmtSettings `mapstructure:"gofmt"`
GoFumpt GoFumptSettings `mapstructure:"gofumpt"`
GoImports GoImportsSettings `mapstructure:"goimports"`
}

type GciSettings struct {
Sections []string `mapstructure:"sections"`
NoInlineComments bool `mapstructure:"no-inline-comments"`
NoPrefixComments bool `mapstructure:"no-prefix-comments"`
SkipGenerated bool `mapstructure:"skip-generated"`
CustomOrder bool `mapstructure:"custom-order"`
NoLexOrder bool `mapstructure:"no-lex-order"`

// Deprecated: use Sections instead.
LocalPrefixes string `mapstructure:"local-prefixes"`
}

type GoFmtSettings struct {
Simplify bool
RewriteRules []GoFmtRewriteRule `mapstructure:"rewrite-rules"`
}

type GoFmtRewriteRule struct {
Pattern string
Replacement string
}

type GoFumptSettings struct {
ModulePath string `mapstructure:"module-path"`
ExtraRules bool `mapstructure:"extra-rules"`

// Deprecated: use the global `run.go` instead.
LangVersion string `mapstructure:"lang-version"`
}

type GoImportsSettings struct {
LocalPrefixes string `mapstructure:"local-prefixes"`
}
6 changes: 6 additions & 0 deletions pkg/config/linters_exclusions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import (
"slices"
)

const (
GeneratedModeLax = "lax"
GeneratedModeStrict = "strict"
GeneratedModeDisable = "disable"
)

const (
ExclusionPresetComments = "comments"
ExclusionPresetStdErrorHandling = "stdErrorHandling"
Expand Down
Loading

0 comments on commit 5a783ba

Please sign in to comment.