// 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") 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] -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 -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{ 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) }