Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new fmt command with dedicated formatter configuration #5357

Merged
merged 13 commits into from
Feb 17, 2025
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)
alexandear marked this conversation as resolved.
Show resolved Hide resolved

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"`
ldez marked this conversation as resolved.
Show resolved Hide resolved
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
Loading