diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 982f05f077c6..f3f967eb669d 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -69,6 +69,7 @@ jobs: - run: ./golangci-lint fmt - run: ./golangci-lint fmt --diff + - run: cat cmd/golangci-lint/main.go | ./golangci-lint fmt --stdin - run: ./golangci-lint cache - run: ./golangci-lint cache status diff --git a/assets/cli-help.json b/assets/cli-help.json index c8027460c012..ccaa4d774caa 100644 --- a/assets/cli-help.json +++ b/assets/cli-help.json @@ -1,5 +1,5 @@ { "enable": "Enabled by default linters:\nerrcheck: Errcheck is a program for checking for unchecked errors in Go code. These unchecked errors can be critical bugs in some cases.\ngovet: Vet examines Go source code and reports suspicious constructs. It is roughly the same as 'go vet' and uses its passes. [auto-fix]\nineffassign: Detects when assignments to existing variables are not used. [fast]\nstaticcheck: It's the set of rules from staticcheck. It's not the same thing as the staticcheck binary. The author of staticcheck doesn't support or approve the use of staticcheck as a library inside golangci-lint. [auto-fix]\nunused: Checks Go code for unused constants, variables, functions and types.", "help": "Usage:\n golangci-lint run [flags]\n\nFlags:\n -c, --config PATH Read config from file path PATH\n --no-config Don't read config file\n --default string Default set of linters to enable (default \"standard\")\n -D, --disable strings Disable specific linter\n -E, --enable strings Enable specific linter\n --enable-only strings Override linters configuration section to only run the specific linter(s)\n --fast-only Filter enabled linters to run only fast linters\n -j, --concurrency int Number of CPUs to use (Default: Automatically set to match Linux container CPU quota and fall back to the number of logical CPUs in the machine)\n --modules-download-mode string Modules download mode. If not empty, passed as -mod=\u003cmode\u003e to go tools\n --issues-exit-code int Exit code when issues were found (default 1)\n --build-tags strings Build tags\n --timeout duration Timeout for total work. Disabled by default\n --tests Analyze tests (*_test.go) (default true)\n --allow-parallel-runners Allow multiple parallel golangci-lint instances running.\n If false (default) - golangci-lint acquires file lock on start.\n --allow-serial-runners Allow multiple golangci-lint instances running, but serialize them around a lock.\n If false (default) - golangci-lint exits with an error if it fails to acquire file lock on start.\n --path-prefix string Path prefix to add to output\n --show-stats Show statistics per linter (default true)\n --output.text.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.text.print-linter-name Print linter name in the end of issue text. (default true)\n --output.text.print-issued-lines Print lines of code with issue. (default true)\n --output.text.colors Use colors. (default true)\n --output.json.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.tab.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.tab.print-linter-name Print linter name in the end of issue text. (default true)\n --output.tab.colors Use colors. (default true)\n --output.html.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.checkstyle.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.code-climate.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.junit-xml.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.junit-xml.extended Support extra JUnit XML fields.\n --output.teamcity.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --output.sarif.path stdout Output path can be either stdout, `stderr` or path to the file to write to.\n --max-issues-per-linter int Maximum issues count per one linter. Set to 0 to disable (default 50)\n --max-same-issues int Maximum count of issues with the same text. Set to 0 to disable (default 3)\n --uniq-by-line Make issues output unique by line (default true)\n -n, --new Show only new issues: if there are unstaged changes or untracked files, only those changes are analyzed, else only changes in HEAD~ are analyzed.\n It's a super-useful option for integration of golangci-lint into existing large codebase.\n It's not practical to fix all existing issues at the moment of integration: much better to not allow issues in new code.\n For CI setups, prefer --new-from-rev=HEAD~, as --new can skip linting the current patch if any scripts generate unstaged files before golangci-lint runs.\n --new-from-rev REV Show only new issues created after git revision REV\n --new-from-patch PATH Show only new issues created in git patch with file path PATH\n --new-from-merge-base string Show only new issues created after the best common ancestor (merge-base against HEAD)\n --whole-files Show issues in any part of update files (requires new-from-rev or new-from-patch)\n --fix Fix found issues (if it's supported by the linter)\n --cpu-profile-path string Path to CPU profile output file\n --mem-profile-path string Path to memory profile output file\n --print-resources-usage Print avg and max memory usage of golangci-lint and total time\n --trace-path string Path to trace output file\n\nGlobal Flags:\n --color string Use color when printing; can be 'always', 'auto', or 'never' (default \"auto\")\n -h, --help Help for a command\n -v, --verbose Verbose output\n", - "fmtHelp": "Usage:\n golangci-lint fmt [flags]\n\nFlags:\n -c, --config PATH Read config from file path PATH\n --no-config Don't read config file\n -E, --enable strings Enable specific formatter\n -d, --diff Display diffs instead of rewriting files\n\nGlobal Flags:\n --color string Use color when printing; can be 'always', 'auto', or 'never' (default \"auto\")\n -h, --help Help for a command\n -v, --verbose Verbose output\n" + "fmtHelp": "Usage:\n golangci-lint fmt [flags]\n\nFlags:\n -c, --config PATH Read config from file path PATH\n --no-config Don't read config file\n -E, --enable strings Enable specific formatter\n -d, --diff Display diffs instead of rewriting files\n --stdin Use standard input for piping source files\n\nGlobal Flags:\n --color string Use color when printing; can be 'always', 'auto', or 'never' (default \"auto\")\n -h, --help Help for a command\n -v, --verbose Verbose output\n" } diff --git a/docs/src/docs/product/migration-guide.mdx b/docs/src/docs/product/migration-guide.mdx index 3cc385461411..9ffba5797f0e 100644 --- a/docs/src/docs/product/migration-guide.mdx +++ b/docs/src/docs/product/migration-guide.mdx @@ -2033,4 +2033,17 @@ The `version` property has been added to the configuration file. ] ``` +You can also add: + +```json +"go.formatTool": "custom", +"go.alternateTools": { + "customFormatter": "golangci-lint" +}, +"go.formatFlags": [ + "fmt", + "--stdin" +] +``` + diff --git a/docs/src/docs/welcome/integrations.mdx b/docs/src/docs/welcome/integrations.mdx index ba0016160c41..406d38b734ea 100644 --- a/docs/src/docs/welcome/integrations.mdx +++ b/docs/src/docs/welcome/integrations.mdx @@ -12,6 +12,15 @@ Recommended settings for VS Code are: "go.lintTool": "golangci-lint", "go.lintFlags": [ "--fast-only" +], + +"go.formatTool": "custom", +"go.alternateTools": { + "customFormatter": "golangci-lint" +}, +"go.formatFlags": [ + "fmt", + "--stdin" ] ``` diff --git a/pkg/commands/fmt.go b/pkg/commands/fmt.go index 355c0404b370..a67be04fd1dc 100644 --- a/pkg/commands/fmt.go +++ b/pkg/commands/fmt.go @@ -20,7 +20,8 @@ import ( type fmtOptions struct { config.LoaderOptions - diff bool // Flag only. + diff bool // Flag only. + stdin bool // Flag only. } type fmtCommand struct { @@ -69,6 +70,7 @@ func newFmtCommand(logger logutils.Log, info BuildInfo) *fmtCommand { setupFormattersFlagSet(c.viper, fs) fs.BoolVarP(&c.opts.diff, "diff", "d", false, color.GreenString("Display diffs instead of rewriting files")) + fs.BoolVar(&c.opts.stdin, "stdin", false, color.GreenString("Use standard input for piping source files")) c.cmd = fmtCmd @@ -100,7 +102,7 @@ func (c *fmtCommand) preRunE(_ *cobra.Command, _ []string) error { matcher := processors.NewGeneratedFileMatcher(c.cfg.Formatters.Exclusions.Generated) - opts, err := goformat.NewRunnerOptions(c.cfg, c.opts.diff) + opts, err := goformat.NewRunnerOptions(c.cfg, c.opts.diff, c.opts.stdin) if err != nil { return fmt.Errorf("build walk options: %w", err) } diff --git a/pkg/goformat/runner.go b/pkg/goformat/runner.go index ebf4dc993c86..4105b9fbf718 100644 --- a/pkg/goformat/runner.go +++ b/pkg/goformat/runner.go @@ -55,6 +55,10 @@ func (c *Runner) Run(paths []string) error { }() } + if c.opts.stdin { + return c.process("", savedStdout, os.Stdin) + } + for _, path := range paths { err := c.walk(path, savedStdout) if err != nil { @@ -84,45 +88,65 @@ func (c *Runner) walk(root string, stdout *os.File) error { return err } - input, err := os.ReadFile(path) + in, err := os.Open(path) if err != nil { return err } - match, err = c.matcher.IsGeneratedFile(path, input) - if err != nil || match { - return err - } + defer func() { _ = in.Close() }() - output := c.metaFormatter.Format(path, input) + return c.process(path, stdout, in) + }) +} - if bytes.Equal(input, output) { - return nil - } +func (c *Runner) process(path string, stdout io.Writer, in io.Reader) error { + input, err := io.ReadAll(in) + if err != nil { + return err + } - if c.opts.diff { - newName := filepath.ToSlash(path) - oldName := newName + ".orig" - _, err = stdout.Write(diff.Diff(oldName, input, newName, output)) - if err != nil { - return err - } + match, err := c.matcher.IsGeneratedFile(path, input) + if err != nil || match { + return err + } - c.exitCode = 1 + output := c.metaFormatter.Format(path, input) - return nil + if c.opts.stdin { + _, err = stdout.Write(output) + if err != nil { + return err } - c.log.Infof("format: %s", path) + return nil + } + + if bytes.Equal(input, output) { + return nil + } - // On Windows, we need to re-set the permissions from the file. See golang/go#38225. - var perms os.FileMode - if fi, err := os.Stat(path); err == nil { - perms = fi.Mode() & os.ModePerm + if c.opts.diff { + newName := filepath.ToSlash(path) + oldName := newName + ".orig" + _, err = stdout.Write(diff.Diff(oldName, input, newName, output)) + if err != nil { + return err } - return os.WriteFile(path, output, perms) - }) + c.exitCode = 1 + + return nil + } + + c.log.Infof("format: %s", path) + + // On Windows, we need to re-set the permissions from the file. See golang/go#38225. + var perms os.FileMode + if fi, err := os.Stat(path); err == nil { + perms = fi.Mode() & os.ModePerm + } + + return os.WriteFile(path, output, perms) } func (c *Runner) setOutputToDevNull() { @@ -144,9 +168,10 @@ type RunnerOptions struct { patterns []*regexp.Regexp generated string diff bool + stdin bool } -func NewRunnerOptions(cfg *config.Config, diff bool) (RunnerOptions, error) { +func NewRunnerOptions(cfg *config.Config, diff, stdin bool) (RunnerOptions, error) { basePath, err := fsutils.GetBasePath(context.Background(), cfg.Run.RelativePathMode, cfg.GetConfigDir()) if err != nil { return RunnerOptions{}, fmt.Errorf("get base path: %w", err) @@ -156,6 +181,7 @@ func NewRunnerOptions(cfg *config.Config, diff bool) (RunnerOptions, error) { basePath: basePath, generated: cfg.Formatters.Exclusions.Generated, diff: diff, + stdin: stdin, } for _, pattern := range cfg.Formatters.Exclusions.Paths {