github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/cmd/eg/eg.go (about) 1 // Copyright 2014 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 // The eg command performs example-based refactoring. 6 // For documentation, run the command, or see Help in 7 // golang.org/x/tools/refactor/eg. 8 package main // import "github.com/jhump/golang-x-tools/cmd/eg" 9 10 import ( 11 "flag" 12 "fmt" 13 "go/ast" 14 "go/format" 15 "go/parser" 16 "go/token" 17 "go/types" 18 "io/ioutil" 19 "os" 20 "path/filepath" 21 "strings" 22 23 exec "golang.org/x/sys/execabs" 24 "github.com/jhump/golang-x-tools/go/packages" 25 "github.com/jhump/golang-x-tools/refactor/eg" 26 ) 27 28 var ( 29 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.") 30 helpFlag = flag.Bool("help", false, "show detailed help message") 31 templateFlag = flag.String("t", "", "template.go file specifying the refactoring") 32 transitiveFlag = flag.Bool("transitive", false, "apply refactoring to all dependencies too") 33 writeFlag = flag.Bool("w", false, "rewrite input files in place (by default, the results are printed to standard output)") 34 verboseFlag = flag.Bool("v", false, "show verbose matcher diagnostics") 35 ) 36 37 const usage = `eg: an example-based refactoring tool. 38 39 Usage: eg -t template.go [-w] [-transitive] <packages> 40 41 -help show detailed help message 42 -t template.go specifies the template file (use -help to see explanation) 43 -w causes files to be re-written in place. 44 -transitive causes all dependencies to be refactored too. 45 -v show verbose matcher diagnostics 46 -beforeedit cmd a command to exec before each file is modified. 47 "{}" represents the name of the file. 48 ` 49 50 func main() { 51 if err := doMain(); err != nil { 52 fmt.Fprintf(os.Stderr, "eg: %s\n", err) 53 os.Exit(1) 54 } 55 } 56 57 func doMain() error { 58 flag.Parse() 59 args := flag.Args() 60 61 if *helpFlag { 62 help := eg.Help // hide %s from vet 63 fmt.Fprint(os.Stderr, help) 64 os.Exit(2) 65 } 66 67 if len(args) == 0 { 68 fmt.Fprint(os.Stderr, usage) 69 os.Exit(1) 70 } 71 72 if *templateFlag == "" { 73 return fmt.Errorf("no -t template.go file specified") 74 } 75 76 tAbs, err := filepath.Abs(*templateFlag) 77 if err != nil { 78 return err 79 } 80 template, err := ioutil.ReadFile(tAbs) 81 if err != nil { 82 return err 83 } 84 85 cfg := &packages.Config{ 86 Fset: token.NewFileSet(), 87 Mode: packages.NeedTypesInfo | packages.NeedName | packages.NeedTypes | packages.NeedSyntax | packages.NeedImports | packages.NeedDeps | packages.NeedCompiledGoFiles, 88 Tests: true, 89 } 90 91 pkgs, err := packages.Load(cfg, args...) 92 if err != nil { 93 return err 94 } 95 96 tFile, err := parser.ParseFile(cfg.Fset, tAbs, template, parser.ParseComments) 97 if err != nil { 98 return err 99 } 100 101 // Type-check the template. 102 tInfo := types.Info{ 103 Types: make(map[ast.Expr]types.TypeAndValue), 104 Defs: make(map[*ast.Ident]types.Object), 105 Uses: make(map[*ast.Ident]types.Object), 106 Implicits: make(map[ast.Node]types.Object), 107 Selections: make(map[*ast.SelectorExpr]*types.Selection), 108 Scopes: make(map[ast.Node]*types.Scope), 109 } 110 conf := types.Config{ 111 Importer: pkgsImporter(pkgs), 112 } 113 tPkg, err := conf.Check("egtemplate", cfg.Fset, []*ast.File{tFile}, &tInfo) 114 if err != nil { 115 return err 116 } 117 118 // Analyze the template. 119 xform, err := eg.NewTransformer(cfg.Fset, tPkg, tFile, &tInfo, *verboseFlag) 120 if err != nil { 121 return err 122 } 123 124 // Apply it to the input packages. 125 var all []*packages.Package 126 if *transitiveFlag { 127 packages.Visit(pkgs, nil, func(p *packages.Package) { all = append(all, p) }) 128 } else { 129 all = pkgs 130 } 131 var hadErrors bool 132 for _, pkg := range pkgs { 133 for i, filename := range pkg.CompiledGoFiles { 134 if filename == tAbs { 135 // Don't rewrite the template file. 136 continue 137 } 138 file := pkg.Syntax[i] 139 n := xform.Transform(pkg.TypesInfo, pkg.Types, file) 140 if n == 0 { 141 continue 142 } 143 fmt.Fprintf(os.Stderr, "=== %s (%d matches)\n", filename, n) 144 if *writeFlag { 145 // Run the before-edit command (e.g. "chmod +w", "checkout") if any. 146 if *beforeeditFlag != "" { 147 args := strings.Fields(*beforeeditFlag) 148 // Replace "{}" with the filename, like find(1). 149 for i := range args { 150 if i > 0 { 151 args[i] = strings.Replace(args[i], "{}", filename, -1) 152 } 153 } 154 cmd := exec.Command(args[0], args[1:]...) 155 cmd.Stdout = os.Stdout 156 cmd.Stderr = os.Stderr 157 if err := cmd.Run(); err != nil { 158 fmt.Fprintf(os.Stderr, "Warning: edit hook %q failed (%s)\n", 159 args, err) 160 } 161 } 162 if err := eg.WriteAST(cfg.Fset, filename, file); err != nil { 163 fmt.Fprintf(os.Stderr, "eg: %s\n", err) 164 hadErrors = true 165 } 166 } else { 167 format.Node(os.Stdout, cfg.Fset, file) 168 } 169 } 170 } 171 if hadErrors { 172 os.Exit(1) 173 } 174 175 return nil 176 } 177 178 type pkgsImporter []*packages.Package 179 180 func (p pkgsImporter) Import(path string) (tpkg *types.Package, err error) { 181 packages.Visit([]*packages.Package(p), func(pkg *packages.Package) bool { 182 if pkg.PkgPath == path { 183 tpkg = pkg.Types 184 return false 185 } 186 return true 187 }, nil) 188 if tpkg != nil { 189 return tpkg, nil 190 } 191 return nil, fmt.Errorf("package %q not found", path) 192 }