Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / mvdan.cc / gofumpt@v0.0.0-20200802201014-ab5a8192947d / gofmt.go
1 // Copyright 2009 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 main
6
7 import (
8         "bytes"
9         "flag"
10         "fmt"
11         "go/ast"
12         "go/parser"
13         "go/printer"
14         "go/scanner"
15         "go/token"
16         "io"
17         "io/ioutil"
18         "os"
19         "os/exec"
20         "path/filepath"
21         "runtime"
22         "runtime/pprof"
23         "strings"
24
25         gformat "mvdan.cc/gofumpt/format"
26         "mvdan.cc/gofumpt/internal/diff"
27 )
28
29 var (
30         // main operation modes
31         list        = flag.Bool("l", false, "list files whose formatting differs from gofumpt's")
32         write       = flag.Bool("w", false, "write result to (source) file instead of stdout")
33         rewriteRule = flag.String("r", "", "rewrite rule (e.g., 'a[b:len(a)] -> a[b:]')")
34         simplifyAST = flag.Bool("s", false, "simplify code")
35         doDiff      = flag.Bool("d", false, "display diffs instead of rewriting files")
36         allErrors   = flag.Bool("e", false, "report all errors (not just the first 10 on different lines)")
37
38         // debugging
39         cpuprofile = flag.String("cpuprofile", "", "write cpu profile to this file")
40 )
41
42 const (
43         tabWidth    = 8
44         printerMode = printer.UseSpaces | printer.TabIndent
45 )
46
47 var (
48         fileSet    = token.NewFileSet() // per process FileSet
49         exitCode   = 0
50         rewrite    func(*ast.File) *ast.File
51         parserMode parser.Mode
52 )
53
54 func report(err error) {
55         scanner.PrintError(os.Stderr, err)
56         exitCode = 2
57 }
58
59 func usage() {
60         fmt.Fprintf(os.Stderr, "usage: gofumpt [flags] [path ...]\n")
61         flag.PrintDefaults()
62 }
63
64 func initParserMode() {
65         parserMode = parser.ParseComments
66         if *allErrors {
67                 parserMode |= parser.AllErrors
68         }
69 }
70
71 func isGoFile(f os.FileInfo) bool {
72         // ignore non-Go files
73         name := f.Name()
74         return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
75 }
76
77 // If in == nil, the source is the contents of the file with the given filename.
78 func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error {
79         var perm os.FileMode = 0o644
80         if in == nil {
81                 f, err := os.Open(filename)
82                 if err != nil {
83                         return err
84                 }
85                 defer f.Close()
86                 fi, err := f.Stat()
87                 if err != nil {
88                         return err
89                 }
90                 in = f
91                 perm = fi.Mode().Perm()
92         }
93
94         src, err := ioutil.ReadAll(in)
95         if err != nil {
96                 return err
97         }
98
99         file, sourceAdj, indentAdj, err := parse(fileSet, filename, src, stdin)
100         if err != nil {
101                 return err
102         }
103
104         if rewrite != nil {
105                 if sourceAdj == nil {
106                         file = rewrite(file)
107                 } else {
108                         fmt.Fprintf(os.Stderr, "warning: rewrite ignored for incomplete programs\n")
109                 }
110         }
111
112         ast.SortImports(fileSet, file)
113
114         if *simplifyAST {
115                 simplify(file)
116         }
117
118         ast.Inspect(file, normalizeNumbers)
119
120         // This is the only gofumpt change on gofumpt's codebase, besides changing
121         // the name in the usage text.
122         if *langVersion == "" {
123                 out, err := exec.Command("go", "list", "-m", "-f", "{{.GoVersion}}").Output()
124                 out = bytes.TrimSpace(out)
125                 if err == nil && len(out) > 0 {
126                         *langVersion = string(out)
127                 }
128         }
129         gformat.File(fileSet, file, gformat.Options{
130                 LangVersion: *langVersion,
131                 ExtraRules:  *extraRules,
132         })
133
134         res, err := format(fileSet, file, sourceAdj, indentAdj, src, printer.Config{Mode: printerMode, Tabwidth: tabWidth})
135         if err != nil {
136                 return err
137         }
138
139         if !bytes.Equal(src, res) {
140                 // formatting has changed
141                 if *list {
142                         fmt.Fprintln(out, filename)
143                 }
144                 if *write {
145                         // make a temporary backup before overwriting original
146                         bakname, err := backupFile(filename+".", src, perm)
147                         if err != nil {
148                                 return err
149                         }
150                         err = ioutil.WriteFile(filename, res, perm)
151                         if err != nil {
152                                 os.Rename(bakname, filename)
153                                 return err
154                         }
155                         err = os.Remove(bakname)
156                         if err != nil {
157                                 return err
158                         }
159                 }
160                 if *doDiff {
161                         data, err := diffWithReplaceTempFile(src, res, filename)
162                         if err != nil {
163                                 return fmt.Errorf("computing diff: %s", err)
164                         }
165                         fmt.Printf("diff -u %s %s\n", filepath.ToSlash(filename+".orig"), filepath.ToSlash(filename))
166                         out.Write(data)
167                 }
168         }
169
170         if !*list && !*write && !*doDiff {
171                 _, err = out.Write(res)
172         }
173
174         return err
175 }
176
177 func visitFile(path string, f os.FileInfo, err error) error {
178         if err == nil && isGoFile(f) {
179                 err = processFile(path, nil, os.Stdout, false)
180         }
181         // Don't complain if a file was deleted in the meantime (i.e.
182         // the directory changed concurrently while running gofumpt).
183         if err != nil && !os.IsNotExist(err) {
184                 report(err)
185         }
186         return nil
187 }
188
189 func walkDir(path string) {
190         filepath.Walk(path, visitFile)
191 }
192
193 func main() {
194         // call gofumptMain in a separate function
195         // so that it can use defer and have them
196         // run before the exit.
197         gofumptMain()
198         os.Exit(exitCode)
199 }
200
201 func gofumptMain() {
202         flag.Usage = usage
203         flag.Parse()
204
205         if *cpuprofile != "" {
206                 f, err := os.Create(*cpuprofile)
207                 if err != nil {
208                         fmt.Fprintf(os.Stderr, "creating cpu profile: %s\n", err)
209                         exitCode = 2
210                         return
211                 }
212                 defer f.Close()
213                 pprof.StartCPUProfile(f)
214                 defer pprof.StopCPUProfile()
215         }
216
217         initParserMode()
218         initRewrite()
219
220         if flag.NArg() == 0 {
221                 if *write {
222                         fmt.Fprintln(os.Stderr, "error: cannot use -w with standard input")
223                         exitCode = 2
224                         return
225                 }
226                 if err := processFile("<standard input>", os.Stdin, os.Stdout, true); err != nil {
227                         report(err)
228                 }
229                 return
230         }
231
232         for i := 0; i < flag.NArg(); i++ {
233                 path := flag.Arg(i)
234                 switch dir, err := os.Stat(path); {
235                 case err != nil:
236                         report(err)
237                 case dir.IsDir():
238                         walkDir(path)
239                 default:
240                         if err := processFile(path, nil, os.Stdout, false); err != nil {
241                                 report(err)
242                         }
243                 }
244         }
245 }
246
247 func diffWithReplaceTempFile(b1, b2 []byte, filename string) ([]byte, error) {
248         data, err := diff.Diff("gofumpt", b1, b2)
249         if len(data) > 0 {
250                 return replaceTempFilename(data, filename)
251         }
252         return data, err
253 }
254
255 // replaceTempFilename replaces temporary filenames in diff with actual one.
256 //
257 // --- /tmp/gofumpt316145376    2017-02-03 19:13:00.280468375 -0500
258 // +++ /tmp/gofumpt617882815    2017-02-03 19:13:00.280468375 -0500
259 // ...
260 // ->
261 // --- path/to/file.go.orig     2017-02-03 19:13:00.280468375 -0500
262 // +++ path/to/file.go  2017-02-03 19:13:00.280468375 -0500
263 // ...
264 func replaceTempFilename(diff []byte, filename string) ([]byte, error) {
265         bs := bytes.SplitN(diff, []byte{'\n'}, 3)
266         if len(bs) < 3 {
267                 return nil, fmt.Errorf("got unexpected diff for %s", filename)
268         }
269         // Preserve timestamps.
270         var t0, t1 []byte
271         if i := bytes.LastIndexByte(bs[0], '\t'); i != -1 {
272                 t0 = bs[0][i:]
273         }
274         if i := bytes.LastIndexByte(bs[1], '\t'); i != -1 {
275                 t1 = bs[1][i:]
276         }
277         // Always print filepath with slash separator.
278         f := filepath.ToSlash(filename)
279         bs[0] = []byte(fmt.Sprintf("--- %s%s", f+".orig", t0))
280         bs[1] = []byte(fmt.Sprintf("+++ %s%s", f, t1))
281         return bytes.Join(bs, []byte{'\n'}), nil
282 }
283
284 const chmodSupported = runtime.GOOS != "windows"
285
286 // backupFile writes data to a new file named filename<number> with permissions perm,
287 // with <number randomly chosen such that the file name is unique. backupFile returns
288 // the chosen file name.
289 func backupFile(filename string, data []byte, perm os.FileMode) (string, error) {
290         // create backup file
291         f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename))
292         if err != nil {
293                 return "", err
294         }
295         bakname := f.Name()
296         if chmodSupported {
297                 err = f.Chmod(perm)
298                 if err != nil {
299                         f.Close()
300                         os.Remove(bakname)
301                         return bakname, err
302                 }
303         }
304
305         // write data to backup file
306         _, err = f.Write(data)
307         if err1 := f.Close(); err == nil {
308                 err = err1
309         }
310
311         return bakname, err
312 }
313
314 // normalizeNumbers rewrites base prefixes and exponents to
315 // use lower-case letters, and removes leading 0's from
316 // integer imaginary literals. It leaves hexadecimal digits
317 // alone.
318 func normalizeNumbers(n ast.Node) bool {
319         lit, _ := n.(*ast.BasicLit)
320         if lit == nil || (lit.Kind != token.INT && lit.Kind != token.FLOAT && lit.Kind != token.IMAG) {
321                 return true
322         }
323         if len(lit.Value) < 2 {
324                 return false // only one digit (common case) - nothing to do
325         }
326         // len(lit.Value) >= 2
327
328         // We ignore lit.Kind because for lit.Kind == token.IMAG the literal may be an integer
329         // or floating-point value, decimal or not. Instead, just consider the literal pattern.
330         x := lit.Value
331         switch x[:2] {
332         default:
333                 // 0-prefix octal, decimal int, or float (possibly with 'i' suffix)
334                 if i := strings.LastIndexByte(x, 'E'); i >= 0 {
335                         x = x[:i] + "e" + x[i+1:]
336                         break
337                 }
338                 // remove leading 0's from integer (but not floating-point) imaginary literals
339                 if x[len(x)-1] == 'i' && strings.IndexByte(x, '.') < 0 && strings.IndexByte(x, 'e') < 0 {
340                         x = strings.TrimLeft(x, "0_")
341                         if x == "i" {
342                                 x = "0i"
343                         }
344                 }
345         case "0X":
346                 x = "0x" + x[2:]
347                 fallthrough
348         case "0x":
349                 // possibly a hexadecimal float
350                 if i := strings.LastIndexByte(x, 'P'); i >= 0 {
351                         x = x[:i] + "p" + x[i+1:]
352                 }
353         case "0O":
354                 x = "0o" + x[2:]
355         case "0o":
356                 // nothing to do
357         case "0B":
358                 x = "0b" + x[2:]
359         case "0b":
360                 // nothing to do
361         }
362
363         lit.Value = x
364         return false
365 }