1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package fmtcmd implements the “go fmt” command.
6package fmtcmd
7
8import (
9	"context"
10	"errors"
11	"fmt"
12	"os"
13	"path/filepath"
14
15	"cmd/go/internal/base"
16	"cmd/go/internal/cfg"
17	"cmd/go/internal/load"
18	"cmd/go/internal/modload"
19	"cmd/internal/sys"
20)
21
22func init() {
23	base.AddBuildFlagsNX(&CmdFmt.Flag)
24	base.AddChdirFlag(&CmdFmt.Flag)
25	base.AddModFlag(&CmdFmt.Flag)
26	base.AddModCommonFlags(&CmdFmt.Flag)
27}
28
29var CmdFmt = &base.Command{
30	Run:       runFmt,
31	UsageLine: "go fmt [-n] [-x] [packages]",
32	Short:     "gofmt (reformat) package sources",
33	Long: `
34Fmt runs the command 'gofmt -l -w' on the packages named
35by the import paths. It prints the names of the files that are modified.
36
37For more about gofmt, see 'go doc cmd/gofmt'.
38For more about specifying packages, see 'go help packages'.
39
40The -n flag prints commands that would be executed.
41The -x flag prints commands as they are executed.
42
43The -mod flag's value sets which module download mode
44to use: readonly or vendor. See 'go help modules' for more.
45
46To run gofmt with specific options, run gofmt itself.
47
48See also: go fix, go vet.
49	`,
50}
51
52func runFmt(ctx context.Context, cmd *base.Command, args []string) {
53	printed := false
54	gofmt := gofmtPath()
55
56	gofmtArgs := []string{gofmt, "-l", "-w"}
57	gofmtArgLen := len(gofmt) + len(" -l -w")
58
59	baseGofmtArgs := len(gofmtArgs)
60	baseGofmtArgLen := gofmtArgLen
61
62	for _, pkg := range load.PackagesAndErrors(ctx, load.PackageOpts{}, args) {
63		if modload.Enabled() && pkg.Module != nil && !pkg.Module.Main {
64			if !printed {
65				fmt.Fprintf(os.Stderr, "go: not formatting packages in dependency modules\n")
66				printed = true
67			}
68			continue
69		}
70		if pkg.Error != nil {
71			var nogo *load.NoGoError
72			var embed *load.EmbedError
73			if (errors.As(pkg.Error, &nogo) || errors.As(pkg.Error, &embed)) && len(pkg.InternalAllGoFiles()) > 0 {
74				// Skip this error, as we will format
75				// all files regardless.
76			} else {
77				base.Errorf("%v", pkg.Error)
78				continue
79			}
80		}
81		// Use pkg.gofiles instead of pkg.Dir so that
82		// the command only applies to this package,
83		// not to packages in subdirectories.
84		files := base.RelPaths(pkg.InternalAllGoFiles())
85		for _, file := range files {
86			gofmtArgs = append(gofmtArgs, file)
87			gofmtArgLen += 1 + len(file) // plus separator
88			if gofmtArgLen >= sys.ExecArgLengthLimit {
89				base.Run(gofmtArgs)
90				gofmtArgs = gofmtArgs[:baseGofmtArgs]
91				gofmtArgLen = baseGofmtArgLen
92			}
93		}
94	}
95	if len(gofmtArgs) > baseGofmtArgs {
96		base.Run(gofmtArgs)
97	}
98}
99
100func gofmtPath() string {
101	gofmt := "gofmt" + cfg.ToolExeSuffix()
102
103	gofmtPath := filepath.Join(cfg.GOBIN, gofmt)
104	if _, err := os.Stat(gofmtPath); err == nil {
105		return gofmtPath
106	}
107
108	gofmtPath = filepath.Join(cfg.GOROOT, "bin", gofmt)
109	if _, err := os.Stat(gofmtPath); err == nil {
110		return gofmtPath
111	}
112
113	// fallback to looking for gofmt in $PATH
114	return "gofmt"
115}
116