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