using ast ang go type to rewrite method

61 views
Skip to first unread message

Yulrizka

unread,
Oct 5, 2019, 7:18:23 AM10/5/19
to golang-nuts
hi

For an experiment, I'm trying to rewrite a method call to use a different package.

for example

package main

import "github.com/pkg/errors"

func main
() {
    err
:= errors.New("original error")
    errors
.Wrap(err, "wrapped error")
}

into

package main

import (
   
"errors"
   
"fmt"
)

func main
() {
    err
:= errors.New("original err")
    fmt
.Errorf("wrapped error: +%v", err)
}

So far I've been using ast functions like this

func processFile(file *ast.File, info *types.Info) {
    ast
.Inspect(file, func(n ast.Node) bool {
       
if n, ok := n.(*ast.CallExpr); ok {
           
if selExpr, ok := n.Fun.(*ast.SelectorExpr); ok {
               
// check if the call uses pkgErrors path
                obj
:= info.Uses[selExpr.Sel]
               
if obj.Pkg().Path() != pkgErrors {
                   
return true
               
}

               
// rename caller package to 'fmt'
               
if x, ok := selExpr.X.(*ast.Ident); ok {
                    x
.Name = "fmt"
               
}

               
// change method call
               
switch selExpr.Sel.Name {
               
case "Wrap":
                    selExpr
.Sel.Name = "Printf"
               
case "Wrapf":
                    selExpr
.Sel.Name = "Printf"
               
}

               
// reverse the arguments
                n
.Args = append(n.Args[1:], n.Args[0])

               
// TODO: format first argument by appending ": %w" in the end
           
}
       
}

       
return true // recur
   
})

    astutil
.AddImport(fset, file, "errors")
    astutil
.DeleteImport(fset, file, pkgErrors)
}

And using this function to test it

func TestProcessFile(t *testing.T) {
   
const src = `package main

import "github.com/pkg/errors"

func main() {
    err := errors.New("original error")
    errors.Wrap(err, "wrapped error")
}`


    file
, err := parser.ParseFile(fset, "input.go", src, parser.AllErrors)
   
if err != nil {
        t
.Fatal(err)
   
}

    conf
:= types.Config{Importer: importer.Default()}
    info
:= &types.Info{
       
Types:      map[ast.Expr]types.TypeAndValue{},
       
Defs:       map[*ast.Ident]types.Object{},
       
Uses:       map[*ast.Ident]types.Object{},
       
Implicits:  map[ast.Node]types.Object{},
       
Selections: map[*ast.SelectorExpr]*types.Selection{},
       
Scopes:     map[ast.Node]*types.Scope{},
   
}

    _
, err = conf.Check("cmd/hello", fset, []*ast.File{file}, info)
   
if err != nil {
        t
.Fatal(err)
   
}

    processFile
(file, info)

   
//ast.Print(fset, file)
    _
= format.Node(os.Stdout, fset, file)

}

So far it gives me the correct output. But I'm wondering if this is the correct approach. It feels like by changing the value of Name I messed up the tokens positions.
Also I've been reading the `gofmt` and `eg`. that uses reflections but I'm not sure if I should use it.

Is there a better approach to achieve the desire result? looking for some pointers to do this.

Regards

Reply all
Reply to author
Forward
0 new messages