Skip to content
Permalink
Browse files

start using 'go generate' to pull upstream code

This makes maintaining files like gofmt.go and internal.go much simpler.
Also empty doc.go, since it made little sense to duplicate all the info,
and since we want to use it for go:generate directives.
  • Loading branch information...
mvdan committed Jun 26, 2019
1 parent 582c384 commit 7ec61abdd409e150ac2f4864b3d76650c5f43de2
Showing with 118 additions and 159 deletions.
  1. +4 −101 doc.go
  2. +75 −0 gen.go
  3. +38 −55 gofmt.go
  4. +1 −3 main_test.go
105 doc.go
@@ -1,104 +1,7 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright (c) 2019, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

/*
Gofmt formats Go programs.
It uses tabs for indentation and blanks for alignment.
Alignment assumes that an editor is using a fixed-width font.
Without an explicit path, it processes the standard input. Given a file,
it operates on that file; given a directory, it operates on all .go files in
that directory, recursively. (Files starting with a period are ignored.)
By default, gofmt prints the reformatted sources to standard output.
Usage:
gofmt [flags] [path ...]
The flags are:
-d
Do not print reformatted sources to standard output.
If a file's formatting is different than gofmt's, print diffs
to standard output.
-e
Print all (including spurious) errors.
-l
Do not print reformatted sources to standard output.
If a file's formatting is different from gofmt's, print its name
to standard output.
-r rule
Apply the rewrite rule to the source before reformatting.
-s
Try to simplify code (after applying the rewrite rule, if any).
-w
Do not print reformatted sources to standard output.
If a file's formatting is different from gofmt's, overwrite it
with gofmt's version. If an error occurred during overwriting,
the original file is restored from an automatic backup.
Debugging support:
-cpuprofile filename
Write cpu profile to the specified file.
The rewrite rule specified with the -r flag must be a string of the form:
pattern -> replacement
Both pattern and replacement must be valid Go expressions.
In the pattern, single-character lowercase identifiers serve as
wildcards matching arbitrary sub-expressions; those expressions
will be substituted for the same identifiers in the replacement.
When gofmt reads from standard input, it accepts either a full Go program
or a program fragment. A program fragment must be a syntactically
valid declaration list, statement list, or expression. When formatting
such a fragment, gofmt preserves leading indentation as well as leading
and trailing spaces, so that individual sections of a Go program can be
formatted by piping them through gofmt.
Examples
To check files for unnecessary parentheses:
gofmt -r '(a) -> a' -l *.go
To remove the parentheses:
gofmt -r '(a) -> a' -w *.go
To convert the package tree from explicit slice upper bounds to implicit ones:
gofmt -r 'α[β:len(α)] -> α[β:]' -w $GOROOT/src
The simplify command
When invoked with -s gofmt will make the following source transformations where possible.
An array, slice, or map composite literal of the form:
[]T{T{}, T{}}
will be simplified to:
[]T{{}, {}}
A slice expression of the form:
s[a:len(s)]
will be simplified to:
s[a:]
A range of the form:
for x, _ = range v {...}
will be simplified to:
for x = range v {...}
A range of the form:
for _ = range v {...}
will be simplified to:
for range v {...}
This may result in changes that are incompatible with earlier versions of Go.
*/
package main

// BUG(rsc): The implementation of -r is a bit slow.
// BUG(gri): If -w fails, the restored original file may not have some of the
// original file attributes.
//go:generate go run gen.go
//go:generate goimports -w gofmt.go
75 gen.go
@@ -0,0 +1,75 @@
// Copyright (c) 2019, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

// +build ignore

package main

import (
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"

"golang.org/x/tools/go/packages"
)

func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

func run() error {
flag.Parse()

cfg := &packages.Config{Mode: packages.NeedName | packages.NeedFiles}
pkgs, err := packages.Load(cfg, "cmd/gofmt")
if err != nil {
return err
}
for _, pkg := range pkgs {
switch pkg.PkgPath {
case "cmd/gofmt":
copyGofmt(pkg.GoFiles)
default:
return fmt.Errorf("unexpected package path %s", pkg.PkgPath)
}
}
return nil
}

func copyGofmt(files []string) error {
const extraSrc = `
// This is the only gofumpt change on gofmt's codebase, besides changing
// the name in the usage text.
internal.Gofumpt(fileSet, file)
`

for _, path := range files {
bodyBytes, err := ioutil.ReadFile(path)
if err != nil {
return err
}
body := string(bodyBytes) // to simplify operations later
name := filepath.Base(path)
switch name {
case "doc.go":
continue // we have our own
case "gofmt.go":
i := strings.Index(body, "res, err := format(")
if i < 0 {
return fmt.Errorf("could not insert the gofumpt source code")
}
body = body[:i] + "\n" + extraSrc + "\n" + body[i:]
}
body = strings.Replace(body, "gofmt", "gofumpt", -1)
if err := ioutil.WriteFile(name, []byte(body), 0644); err != nil {
return err
}
}
return nil
}
@@ -116,7 +116,7 @@ func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error

ast.Inspect(file, normalizeNumbers)

// This is the only gofumpt change on gofmt's codebase, besides changing
// This is the only gofumpt change on gofumpt's codebase, besides changing
// the name in the usage text.
internal.Gofumpt(fileSet, file)

@@ -168,7 +168,7 @@ func visitFile(path string, f os.FileInfo, err error) error {
err = processFile(path, nil, os.Stdout, false)
}
// Don't complain if a file was deleted in the meantime (i.e.
// the directory changed concurrently while running gofmt).
// the directory changed concurrently while running gofumpt).
if err != nil && !os.IsNotExist(err) {
report(err)
}
@@ -180,14 +180,14 @@ func walkDir(path string) {
}

func main() {
// call gofmtMain in a separate function
// call gofumptMain in a separate function
// so that it can use defer and have them
// run before the exit.
gofmtMain()
gofumptMain()
os.Exit(exitCode)
}

func gofmtMain() {
func gofumptMain() {
flag.Usage = usage
flag.Parse()

@@ -250,13 +250,13 @@ func writeTempFile(dir, prefix string, data []byte) (string, error) {
}

func diff(b1, b2 []byte, filename string) (data []byte, err error) {
f1, err := writeTempFile("", "gofmt", b1)
f1, err := writeTempFile("", "gofumpt", b1)
if err != nil {
return
}
defer os.Remove(f1)

f2, err := writeTempFile("", "gofmt", b2)
f2, err := writeTempFile("", "gofumpt", b2)
if err != nil {
return
}
@@ -278,8 +278,8 @@ func diff(b1, b2 []byte, filename string) (data []byte, err error) {

// replaceTempFilename replaces temporary filenames in diff with actual one.
//
// --- /tmp/gofmt316145376 2017-02-03 19:13:00.280468375 -0500
// +++ /tmp/gofmt617882815 2017-02-03 19:13:00.280468375 -0500
// --- /tmp/gofumpt316145376 2017-02-03 19:13:00.280468375 -0500
// +++ /tmp/gofumpt617882815 2017-02-03 19:13:00.280468375 -0500
// ...
// ->
// --- path/to/file.go.orig 2017-02-03 19:13:00.280468375 -0500
@@ -341,66 +341,49 @@ func backupFile(filename string, data []byte, perm os.FileMode) (string, error)
// alone.
func normalizeNumbers(n ast.Node) bool {
lit, _ := n.(*ast.BasicLit)
if lit == nil {
if lit == nil || (lit.Kind != token.INT && lit.Kind != token.FLOAT && lit.Kind != token.IMAG) {
return true
}
if len(lit.Value) < 2 {
return false // only one digit - nothing to do
return false // only one digit (common case) - nothing to do
}
// len(lit.Value) >= 2

// We ignore lit.Kind because for lit.Kind == token.IMAG the literal may be an integer
// or floating-point value, decimal or not. Instead, just consider the literal pattern.
x := lit.Value
switch lit.Kind {
case token.INT:
switch x[:2] {
case "0X":
lit.Value = "0x" + x[2:]
case "0O":
lit.Value = "0o" + x[2:]
case "0B":
lit.Value = "0b" + x[2:]
switch x[:2] {
default:
// 0-prefix octal, decimal int, or float (possibly with 'i' suffix)
if i := strings.LastIndexByte(x, 'E'); i >= 0 {
x = x[:i] + "e" + x[i+1:]
break
}

case token.FLOAT:
switch lit.Value[:2] {
default:
if i := strings.LastIndexByte(x, 'E'); i >= 0 {
lit.Value = x[:i] + "e" + x[i+1:]
}
case "0x":
if i := strings.LastIndexByte(x, 'P'); i >= 0 {
lit.Value = x[:i] + "p" + x[i+1:]
}
case "0X":
if i := strings.LastIndexByte(x, 'P'); i >= 0 {
lit.Value = "0x" + x[2:i] + "p" + x[i+1:]
} else {
lit.Value = "0x" + x[2:]
}
}

case token.IMAG:
// Note that integer imaginary literals may contain
// any decimal digit even if they start with zero.
// Imaginary literals should always end in 'i' but be
// conservative and check anyway before proceeding.
if x[0] == '0' && x[len(x)-1] == 'i' && isDecimals(x[1:len(x)-1]) {
// remove leading 0's from integer (but not floating-point) imaginary literals
if x[len(x)-1] == 'i' && strings.IndexByte(x, '.') < 0 && strings.IndexByte(x, 'e') < 0 {
x = strings.TrimLeft(x, "0_")
if x == "i" {
x = "0i"
}
lit.Value = x
}
case "0X":
x = "0x" + x[2:]
fallthrough
case "0x":
// possibly a hexadecimal float
if i := strings.LastIndexByte(x, 'P'); i >= 0 {
x = x[:i] + "p" + x[i+1:]
}
case "0O":
x = "0o" + x[2:]
case "0o":
// nothing to do
case "0B":
x = "0b" + x[2:]
case "0b":
// nothing to do
}

lit.Value = x
return false
}

// isDecimals reports whether x consists entirely of decimal digits and underscores.
func isDecimals(x string) bool {
i := 0
for i < len(x) && ('0' <= x[i] && x[i] <= '9' || x[i] == '_') {
i++
}
return i == len(x)
}
@@ -14,9 +14,7 @@ import (
func TestMain(m *testing.M) {
os.Exit(testscript.RunMain(m, map[string]func() int{
"gofumpt": func() int {
// Don't change gofmtMain, to keep changes to the gofmt
// codebase to a minimum.
gofmtMain()
gofumptMain()
return exitCode
},
}))

0 comments on commit 7ec61ab

Please sign in to comment.
You can’t perform that action at this time.