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