| // Copyright 2014 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. | |
| // The eg command performs example-based refactoring. | |
| // For documentation, run the command, or see Help in | |
| // golang.org/x/tools/refactor/eg. | |
| package main // import "golang.org/x/tools/cmd/eg" | |
| import ( | |
| "flag" | |
| "fmt" | |
| "go/ast" | |
| "go/format" | |
| "go/parser" | |
| "go/token" | |
| "go/types" | |
| "os" | |
| "os/exec" | |
| "path/filepath" | |
| "strings" | |
| "golang.org/x/tools/go/packages" | |
| "golang.org/x/tools/refactor/eg" | |
| ) | |
| var ( | |
| beforeeditFlag = flag.String("beforeedit", "", "A command to exec before each file is edited (e.g. chmod, checkout). Whitespace delimits argument words. The string '{}' is replaced by the file name.") | |
| helpFlag = flag.Bool("help", false, "show detailed help message") | |
| tagsFlag = flag.String("tags", "", "comma-separated list of build tags to apply (see: go help buildconstraint)") | |
| templateFlag = flag.String("t", "", "template.go file specifying the refactoring") | |
| transitiveFlag = flag.Bool("transitive", false, "apply refactoring to all dependencies too") | |
| writeFlag = flag.Bool("w", false, "rewrite input files in place (by default, the results are printed to standard output)") | |
| verboseFlag = flag.Bool("v", false, "show verbose matcher diagnostics") | |
| ) | |
| const usage = `eg: an example-based refactoring tool. | |
| Usage: eg -t template.go [-w] [-transitive] [-tags=tag,...] <packages> | |
| -help show detailed help message | |
| -t template.go specifies the template file (use -help to see explanation) | |
| -w causes files to be re-written in place. | |
| -transitive causes all dependencies to be refactored too. | |
| -v show verbose matcher diagnostics | |
| -tags tag,... comma-separated list of build tags to apply (see: go help buildconstraint) | |
| -beforeedit cmd a command to exec before each file is modified. | |
| "{}" represents the name of the file. | |
| ` | |
| func main() { | |
| if err := doMain(); err != nil { | |
| fmt.Fprintf(os.Stderr, "eg: %s\n", err) | |
| os.Exit(1) | |
| } | |
| } | |
| func doMain() error { | |
| flag.Parse() | |
| args := flag.Args() | |
| if *helpFlag { | |
| help := eg.Help // hide %s from vet | |
| fmt.Fprint(os.Stderr, help) | |
| os.Exit(2) | |
| } | |
| if len(args) == 0 { | |
| fmt.Fprint(os.Stderr, usage) | |
| os.Exit(1) | |
| } | |
| if *templateFlag == "" { | |
| return fmt.Errorf("no -t template.go file specified") | |
| } | |
| tAbs, err := filepath.Abs(*templateFlag) | |
| if err != nil { | |
| return err | |
| } | |
| template, err := os.ReadFile(tAbs) | |
| if err != nil { | |
| return err | |
| } | |
| cfg := &packages.Config{ | |
| BuildFlags: []string{"-tags=" + *tagsFlag}, | |
| Fset: token.NewFileSet(), | |
| Mode: packages.NeedTypesInfo | packages.NeedName | packages.NeedTypes | packages.NeedSyntax | packages.NeedImports | packages.NeedDeps | packages.NeedCompiledGoFiles, | |
| Tests: true, | |
| } | |
| pkgs, err := packages.Load(cfg, args...) | |
| if err != nil { | |
| return err | |
| } | |
| tFile, err := parser.ParseFile(cfg.Fset, tAbs, template, parser.ParseComments) | |
| if err != nil { | |
| return err | |
| } | |
| // Type-check the template. | |
| tInfo := types.Info{ | |
| Types: make(map[ast.Expr]types.TypeAndValue), | |
| Defs: make(map[*ast.Ident]types.Object), | |
| Uses: make(map[*ast.Ident]types.Object), | |
| Implicits: make(map[ast.Node]types.Object), | |
| Selections: make(map[*ast.SelectorExpr]*types.Selection), | |
| Scopes: make(map[ast.Node]*types.Scope), | |
| } | |
| conf := types.Config{ | |
| Importer: pkgsImporter(pkgs), | |
| } | |
| tPkg, err := conf.Check("egtemplate", cfg.Fset, []*ast.File{tFile}, &tInfo) | |
| if err != nil { | |
| return err | |
| } | |
| // Analyze the template. | |
| xform, err := eg.NewTransformer(cfg.Fset, tPkg, tFile, &tInfo, *verboseFlag) | |
| if err != nil { | |
| return err | |
| } | |
| // Apply it to the input packages. | |
| var all []*packages.Package | |
| if *transitiveFlag { | |
| packages.Visit(pkgs, nil, func(p *packages.Package) { all = append(all, p) }) | |
| } else { | |
| all = pkgs | |
| } | |
| var hadErrors bool | |
| for _, pkg := range pkgs { | |
| for i, filename := range pkg.CompiledGoFiles { | |
| if filename == tAbs { | |
| // Don't rewrite the template file. | |
| continue | |
| } | |
| file := pkg.Syntax[i] | |
| n := xform.Transform(pkg.TypesInfo, pkg.Types, file) | |
| if n == 0 { | |
| continue | |
| } | |
| fmt.Fprintf(os.Stderr, "=== %s (%d matches)\n", filename, n) | |
| if *writeFlag { | |
| // Run the before-edit command (e.g. "chmod +w", "checkout") if any. | |
| if *beforeeditFlag != "" { | |
| args := strings.Fields(*beforeeditFlag) | |
| // Replace "{}" with the filename, like find(1). | |
| for i := range args { | |
| if i > 0 { | |
| args[i] = strings.Replace(args[i], "{}", filename, -1) | |
| } | |
| } | |
| cmd := exec.Command(args[0], args[1:]...) | |
| cmd.Stdout = os.Stdout | |
| cmd.Stderr = os.Stderr | |
| if err := cmd.Run(); err != nil { | |
| fmt.Fprintf(os.Stderr, "Warning: edit hook %q failed (%s)\n", | |
| args, err) | |
| } | |
| } | |
| if err := eg.WriteAST(cfg.Fset, filename, file); err != nil { | |
| fmt.Fprintf(os.Stderr, "eg: %s\n", err) | |
| hadErrors = true | |
| } | |
| } else { | |
| format.Node(os.Stdout, cfg.Fset, file) | |
| } | |
| } | |
| } | |
| if hadErrors { | |
| os.Exit(1) | |
| } | |
| return nil | |
| } | |
| type pkgsImporter []*packages.Package | |
| func (p pkgsImporter) Import(path string) (tpkg *types.Package, err error) { | |
| packages.Visit([]*packages.Package(p), func(pkg *packages.Package) bool { | |
| if pkg.PkgPath == path { | |
| tpkg = pkg.Types | |
| return false | |
| } | |
| return true | |
| }, nil) | |
| if tpkg != nil { | |
| return tpkg, nil | |
| } | |
| return nil, fmt.Errorf("package %q not found", path) | |
| } |