github.com/golangci/go-tools@v0.0.0-20190318060251-af6baa5dc196/cmd/keyify/keyify.go (about)

     1  // keyify transforms unkeyed struct literals into a keyed ones.
     2  package main
     3  
     4  import (
     5  	"bytes"
     6  	"encoding/json"
     7  	"flag"
     8  	"fmt"
     9  	"go/ast"
    10  	"go/build"
    11  	"go/constant"
    12  	"go/printer"
    13  	"go/token"
    14  	"go/types"
    15  	"log"
    16  	"os"
    17  	"path/filepath"
    18  
    19  	"github.com/golangci/go-tools/version"
    20  
    21  	"golang.org/x/tools/go/ast/astutil"
    22  	"golang.org/x/tools/go/buildutil"
    23  	"golang.org/x/tools/go/loader"
    24  )
    25  
    26  var (
    27  	fRecursive bool
    28  	fOneLine   bool
    29  	fJSON      bool
    30  	fMinify    bool
    31  	fModified  bool
    32  	fVersion   bool
    33  )
    34  
    35  func init() {
    36  	flag.BoolVar(&fRecursive, "r", false, "keyify struct initializers recursively")
    37  	flag.BoolVar(&fOneLine, "o", false, "print new struct initializer on a single line")
    38  	flag.BoolVar(&fJSON, "json", false, "print new struct initializer as JSON")
    39  	flag.BoolVar(&fMinify, "m", false, "omit fields that are set to their zero value")
    40  	flag.BoolVar(&fModified, "modified", false, "read an archive of modified files from standard input")
    41  	flag.BoolVar(&fVersion, "version", false, "Print version and exit")
    42  }
    43  
    44  func usage() {
    45  	fmt.Printf("Usage: %s [flags] <position>\n\n", os.Args[0])
    46  	flag.PrintDefaults()
    47  }
    48  
    49  func main() {
    50  	log.SetFlags(0)
    51  	flag.Usage = usage
    52  	flag.Parse()
    53  
    54  	if fVersion {
    55  		version.Print()
    56  		os.Exit(0)
    57  	}
    58  
    59  	if flag.NArg() != 1 {
    60  		flag.Usage()
    61  		os.Exit(2)
    62  	}
    63  	pos := flag.Args()[0]
    64  	name, start, _, err := parsePos(pos)
    65  	if err != nil {
    66  		log.Fatal(err)
    67  	}
    68  	eval, err := filepath.EvalSymlinks(name)
    69  	if err != nil {
    70  		log.Fatal(err)
    71  	}
    72  	name, err = filepath.Abs(eval)
    73  	if err != nil {
    74  		log.Fatal(err)
    75  	}
    76  	cwd, err := os.Getwd()
    77  	if err != nil {
    78  		log.Fatal(err)
    79  	}
    80  	ctx := &build.Default
    81  	if fModified {
    82  		overlay, err := buildutil.ParseOverlayArchive(os.Stdin)
    83  		if err != nil {
    84  			log.Fatal(err)
    85  		}
    86  		ctx = buildutil.OverlayContext(ctx, overlay)
    87  	}
    88  	bpkg, err := buildutil.ContainingPackage(ctx, cwd, name)
    89  	if err != nil {
    90  		log.Fatal(err)
    91  	}
    92  	conf := &loader.Config{
    93  		Build: ctx,
    94  	}
    95  	conf.TypeCheckFuncBodies = func(s string) bool {
    96  		return s == bpkg.ImportPath || s == bpkg.ImportPath+"_test"
    97  	}
    98  	conf.ImportWithTests(bpkg.ImportPath)
    99  	lprog, err := conf.Load()
   100  	if err != nil {
   101  		log.Fatal(err)
   102  	}
   103  	var tf *token.File
   104  	var af *ast.File
   105  	var pkg *loader.PackageInfo
   106  outer:
   107  	for _, pkg = range lprog.InitialPackages() {
   108  		for _, ff := range pkg.Files {
   109  			file := lprog.Fset.File(ff.Pos())
   110  			if file.Name() == name {
   111  				af = ff
   112  				tf = file
   113  				break outer
   114  			}
   115  		}
   116  	}
   117  	if tf == nil {
   118  		log.Fatalf("couldn't find file %s", name)
   119  	}
   120  	tstart, tend, err := fileOffsetToPos(tf, start, start)
   121  	if err != nil {
   122  		log.Fatal(err)
   123  	}
   124  	path, _ := astutil.PathEnclosingInterval(af, tstart, tend)
   125  	var complit *ast.CompositeLit
   126  	for _, p := range path {
   127  		if p, ok := p.(*ast.CompositeLit); ok {
   128  			complit = p
   129  			break
   130  		}
   131  	}
   132  	if complit == nil {
   133  		log.Fatal("no composite literal found near point")
   134  	}
   135  	if len(complit.Elts) == 0 {
   136  		printComplit(complit, complit, lprog.Fset, lprog.Fset)
   137  		return
   138  	}
   139  	if _, ok := complit.Elts[0].(*ast.KeyValueExpr); ok {
   140  		lit := complit
   141  		if fOneLine {
   142  			lit = copyExpr(complit, 1).(*ast.CompositeLit)
   143  		}
   144  		printComplit(complit, lit, lprog.Fset, lprog.Fset)
   145  		return
   146  	}
   147  	_, ok := pkg.TypeOf(complit).Underlying().(*types.Struct)
   148  	if !ok {
   149  		log.Fatal("not a struct initialiser")
   150  		return
   151  	}
   152  
   153  	newComplit, lines := keyify(pkg, complit)
   154  	newFset := token.NewFileSet()
   155  	newFile := newFset.AddFile("", -1, lines)
   156  	for i := 1; i <= lines; i++ {
   157  		newFile.AddLine(i)
   158  	}
   159  	printComplit(complit, newComplit, lprog.Fset, newFset)
   160  }
   161  
   162  func keyify(
   163  	pkg *loader.PackageInfo,
   164  	complit *ast.CompositeLit,
   165  ) (*ast.CompositeLit, int) {
   166  	var calcPos func(int) token.Pos
   167  	if fOneLine {
   168  		calcPos = func(int) token.Pos { return token.Pos(1) }
   169  	} else {
   170  		calcPos = func(i int) token.Pos { return token.Pos(2 + i) }
   171  	}
   172  
   173  	st, _ := pkg.TypeOf(complit).Underlying().(*types.Struct)
   174  	newComplit := &ast.CompositeLit{
   175  		Type:   complit.Type,
   176  		Lbrace: 1,
   177  		Rbrace: token.Pos(st.NumFields() + 2),
   178  	}
   179  	if fOneLine {
   180  		newComplit.Rbrace = 1
   181  	}
   182  	numLines := 2 + st.NumFields()
   183  	n := 0
   184  	for i := 0; i < st.NumFields(); i++ {
   185  		field := st.Field(i)
   186  		val := complit.Elts[i]
   187  		if fRecursive {
   188  			if val2, ok := val.(*ast.CompositeLit); ok {
   189  				if _, ok := pkg.TypeOf(val2.Type).Underlying().(*types.Struct); ok {
   190  					var lines int
   191  					numLines += lines
   192  					val, lines = keyify(pkg, val2)
   193  				}
   194  			}
   195  		}
   196  		_, isIface := st.Field(i).Type().Underlying().(*types.Interface)
   197  		if fMinify && (isNil(val, pkg) || (!isIface && isZero(val, pkg))) {
   198  			continue
   199  		}
   200  		elt := &ast.KeyValueExpr{
   201  			Key:   &ast.Ident{NamePos: calcPos(n), Name: field.Name()},
   202  			Value: copyExpr(val, calcPos(n)),
   203  		}
   204  		newComplit.Elts = append(newComplit.Elts, elt)
   205  		n++
   206  	}
   207  	return newComplit, numLines
   208  }
   209  
   210  func isNil(val ast.Expr, pkg *loader.PackageInfo) bool {
   211  	ident, ok := val.(*ast.Ident)
   212  	if !ok {
   213  		return false
   214  	}
   215  	if _, ok := pkg.ObjectOf(ident).(*types.Nil); ok {
   216  		return true
   217  	}
   218  	if c, ok := pkg.ObjectOf(ident).(*types.Const); ok {
   219  		if c.Val().Kind() != constant.Bool {
   220  			return false
   221  		}
   222  		return !constant.BoolVal(c.Val())
   223  	}
   224  	return false
   225  }
   226  
   227  func isZero(val ast.Expr, pkg *loader.PackageInfo) bool {
   228  	switch val := val.(type) {
   229  	case *ast.BasicLit:
   230  		switch val.Value {
   231  		case `""`, "``", "0", "0.0", "0i", "0.":
   232  			return true
   233  		default:
   234  			return false
   235  		}
   236  	case *ast.Ident:
   237  		return isNil(val, pkg)
   238  	case *ast.CompositeLit:
   239  		typ := pkg.TypeOf(val.Type)
   240  		if typ == nil {
   241  			return false
   242  		}
   243  		isIface := false
   244  		switch typ := typ.Underlying().(type) {
   245  		case *types.Struct:
   246  		case *types.Array:
   247  			_, isIface = typ.Elem().Underlying().(*types.Interface)
   248  		default:
   249  			return false
   250  		}
   251  		for _, elt := range val.Elts {
   252  			if isNil(elt, pkg) || (!isIface && !isZero(elt, pkg)) {
   253  				return false
   254  			}
   255  		}
   256  		return true
   257  	}
   258  	return false
   259  }
   260  
   261  func printComplit(oldlit, newlit *ast.CompositeLit, oldfset, newfset *token.FileSet) {
   262  	buf := &bytes.Buffer{}
   263  	cfg := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
   264  	_ = cfg.Fprint(buf, newfset, newlit)
   265  	if fJSON {
   266  		output := struct {
   267  			Start       int    `json:"start"`
   268  			End         int    `json:"end"`
   269  			Replacement string `json:"replacement"`
   270  		}{
   271  			oldfset.Position(oldlit.Pos()).Offset,
   272  			oldfset.Position(oldlit.End()).Offset,
   273  			buf.String(),
   274  		}
   275  		_ = json.NewEncoder(os.Stdout).Encode(output)
   276  	} else {
   277  		fmt.Println(buf.String())
   278  	}
   279  }
   280  
   281  func copyExpr(expr ast.Expr, line token.Pos) ast.Expr {
   282  	switch expr := expr.(type) {
   283  	case *ast.BasicLit:
   284  		cp := *expr
   285  		cp.ValuePos = 0
   286  		return &cp
   287  	case *ast.BinaryExpr:
   288  		cp := *expr
   289  		cp.X = copyExpr(cp.X, line)
   290  		cp.OpPos = 0
   291  		cp.Y = copyExpr(cp.Y, line)
   292  		return &cp
   293  	case *ast.CallExpr:
   294  		cp := *expr
   295  		cp.Fun = copyExpr(cp.Fun, line)
   296  		cp.Lparen = 0
   297  		for i, v := range cp.Args {
   298  			cp.Args[i] = copyExpr(v, line)
   299  		}
   300  		if cp.Ellipsis != 0 {
   301  			cp.Ellipsis = line
   302  		}
   303  		cp.Rparen = 0
   304  		return &cp
   305  	case *ast.CompositeLit:
   306  		cp := *expr
   307  		cp.Type = copyExpr(cp.Type, line)
   308  		cp.Lbrace = 0
   309  		for i, v := range cp.Elts {
   310  			cp.Elts[i] = copyExpr(v, line)
   311  		}
   312  		cp.Rbrace = 0
   313  		return &cp
   314  	case *ast.Ident:
   315  		cp := *expr
   316  		cp.NamePos = 0
   317  		return &cp
   318  	case *ast.IndexExpr:
   319  		cp := *expr
   320  		cp.X = copyExpr(cp.X, line)
   321  		cp.Lbrack = 0
   322  		cp.Index = copyExpr(cp.Index, line)
   323  		cp.Rbrack = 0
   324  		return &cp
   325  	case *ast.KeyValueExpr:
   326  		cp := *expr
   327  		cp.Key = copyExpr(cp.Key, line)
   328  		cp.Colon = 0
   329  		cp.Value = copyExpr(cp.Value, line)
   330  		return &cp
   331  	case *ast.ParenExpr:
   332  		cp := *expr
   333  		cp.Lparen = 0
   334  		cp.X = copyExpr(cp.X, line)
   335  		cp.Rparen = 0
   336  		return &cp
   337  	case *ast.SelectorExpr:
   338  		cp := *expr
   339  		cp.X = copyExpr(cp.X, line)
   340  		cp.Sel = copyExpr(cp.Sel, line).(*ast.Ident)
   341  		return &cp
   342  	case *ast.SliceExpr:
   343  		cp := *expr
   344  		cp.X = copyExpr(cp.X, line)
   345  		cp.Lbrack = 0
   346  		cp.Low = copyExpr(cp.Low, line)
   347  		cp.High = copyExpr(cp.High, line)
   348  		cp.Max = copyExpr(cp.Max, line)
   349  		cp.Rbrack = 0
   350  		return &cp
   351  	case *ast.StarExpr:
   352  		cp := *expr
   353  		cp.Star = 0
   354  		cp.X = copyExpr(cp.X, line)
   355  		return &cp
   356  	case *ast.TypeAssertExpr:
   357  		cp := *expr
   358  		cp.X = copyExpr(cp.X, line)
   359  		cp.Lparen = 0
   360  		cp.Type = copyExpr(cp.Type, line)
   361  		cp.Rparen = 0
   362  		return &cp
   363  	case *ast.UnaryExpr:
   364  		cp := *expr
   365  		cp.OpPos = 0
   366  		cp.X = copyExpr(cp.X, line)
   367  		return &cp
   368  	case *ast.MapType:
   369  		cp := *expr
   370  		cp.Map = 0
   371  		cp.Key = copyExpr(cp.Key, line)
   372  		cp.Value = copyExpr(cp.Value, line)
   373  		return &cp
   374  	case *ast.ArrayType:
   375  		cp := *expr
   376  		cp.Lbrack = 0
   377  		cp.Len = copyExpr(cp.Len, line)
   378  		cp.Elt = copyExpr(cp.Elt, line)
   379  		return &cp
   380  	case *ast.Ellipsis:
   381  		cp := *expr
   382  		cp.Elt = copyExpr(cp.Elt, line)
   383  		cp.Ellipsis = line
   384  		return &cp
   385  	case *ast.InterfaceType:
   386  		cp := *expr
   387  		cp.Interface = 0
   388  		return &cp
   389  	case *ast.StructType:
   390  		cp := *expr
   391  		cp.Struct = 0
   392  		return &cp
   393  	case *ast.FuncLit:
   394  		return expr
   395  	case *ast.ChanType:
   396  		cp := *expr
   397  		cp.Arrow = 0
   398  		cp.Begin = 0
   399  		cp.Value = copyExpr(cp.Value, line)
   400  		return &cp
   401  	case nil:
   402  		return nil
   403  	default:
   404  		panic(fmt.Sprintf("shouldn't happen: unknown ast.Expr of type %T", expr))
   405  	}
   406  }