package fix import ( "bytes" "fmt" "go/ast" "go/parser" "go/printer" "go/token" "io/ioutil" "os" "path/filepath" "strconv" "strings" "golang.org/x/tools/go/ast/astutil" ) // ImportConverter will changes imports from a -> b type ImportConverter struct { Data map[string]string } // Process will walk all the .go files in an application, excluding ./vendor. // It will then attempt to convert any old import paths to any new import paths // used by this version Buffalo. func (c ImportConverter) Process(r *Runner) error { fmt.Println("~~~ Rewriting Imports ~~~") err := filepath.Walk(".", c.processFile) if err != nil { return err } if _, err := os.Stat("Gopkg.toml"); err != nil { return nil } b, err := ioutil.ReadFile("Gopkg.toml") if err != nil { return err } for k := range c.Data { if bytes.Contains(b, []byte(k)) { r.Warnings = append(r.Warnings, fmt.Sprintf("Your Gopkg.toml contains the following import that need to be changed MANUALLY: %s", k)) } } return nil } func (c ImportConverter) processFile(p string, info os.FileInfo, err error) error { er := onlyRelevantFiles(p, info, err, func(p string) error { err := c.rewriteFile(p) if err != nil { err = err } return err }) return er } func (c ImportConverter) rewriteFile(name string) error { // create an empty fileset. fset := token.NewFileSet() // parse the .go file. // we are parsing the entire file with comments, so we don't lose anything // if we need to write it back out. f, err := parser.ParseFile(fset, name, nil, parser.ParseComments) if err != nil { e := err.Error() msg := "expected 'package', found 'EOF'" if e[len(e)-len(msg):] == msg { return nil } return err } changed := false funcs := []*ast.FuncDecl{} for _, d := range f.Decls { if fn, isFn := d.(*ast.FuncDecl); isFn { funcs = append(funcs, fn) } } for _, fun := range funcs { ast.Inspect(fun, func(node ast.Node) bool { switch n := node.(type) { case *ast.CallExpr: fn, ok := n.Fun.(*ast.SelectorExpr) if !ok || fn.Sel == nil { return true } sel := fn.Sel i, ok := fn.X.(*ast.Ident) if !ok { return true } if i.Name != "packr" { return true } if sel.Name == "NewBox" { sel.Name = "New" n.Args = append(n.Args, n.Args[0]) changed = true } if sel.Name == "MustBytes" { sel.Name = "Find" changed = true } if sel.Name == "MustBytes" { sel.Name = "Find" changed = true } } return true }) } for key, value := range c.Data { if !astutil.DeleteImport(fset, f, key) { continue } astutil.AddImport(fset, f, value) changed = true } commentsChanged, err := c.handleFileComments(f) if err != nil { return err } changed = changed || commentsChanged // if no change occurred, then we don't need to write to disk, just return. if !changed { return nil } // since the imports changed, resort them. ast.SortImports(fset, f) // create a temporary file, this easily avoids conflicts. temp, err := writeTempResult(name, fset, f) if err != nil { return err } // rename the .temp to .go return os.Rename(temp, name) } func (c ImportConverter) handleFileComments(f *ast.File) (bool, error) { change := false for _, cg := range f.Comments { for _, cl := range cg.List { if !strings.HasPrefix(cl.Text, "// import \"") { continue } // trim off extra comment stuff ctext := cl.Text ctext = strings.TrimPrefix(ctext, "// import") ctext = strings.TrimSpace(ctext) // unquote the comment import path value ctext, err := strconv.Unquote(ctext) if err != nil { return false, err } // match the comment import path with the given replacement map if ctext, ok := c.match(ctext); ok { cl.Text = "// import " + strconv.Quote(ctext) change = true } } } return change, nil } // match takes an import path and replacement map. func (c ImportConverter) match(importpath string) (string, bool) { for key, value := range c.Data { if !strings.HasPrefix(importpath, key) { continue } result := strings.Replace(importpath, key, value, 1) return result, true } return importpath, false } //onlyRelevantFiles processes only .go files excluding folders like node_modules and vendor. func onlyRelevantFiles(p string, fi os.FileInfo, err error, fn func(p string) error) error { if err != nil { return err } if fi.IsDir() && p != "." { for _, n := range []string{"_", ".", "vendor", "node_modules", ".git"} { base := filepath.Base(p) if strings.HasPrefix(base, n) { return filepath.SkipDir } } return nil } ext := filepath.Ext(p) if ext != ".go" { return nil } return fn(p) } func writeTempResult(name string, fset *token.FileSet, f *ast.File) (string, error) { temp := name + ".temp" w, err := os.Create(temp) if err != nil { return "", err } // write changes to .temp file, and include proper formatting. err = (&printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 8}).Fprint(w, fset, f) if err != nil { return "", err } // close the writer err = w.Close() if err != nil { return "", err } return temp, nil }