github.com/pankona/gometalinter@v2.0.11+incompatible/_linters/src/honnef.co/go/tools/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  	"honnef.co/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  	pkg := lprog.InitialPackages()[0]
   106  	for _, ff := range pkg.Files {
   107  		file := lprog.Fset.File(ff.Pos())
   108  		if file.Name() == name {
   109  			af = ff
   110  			tf = file
   111  			break
   112  		}
   113  	}
   114  	tstart, tend, err := fileOffsetToPos(tf, start, start)
   115  	if err != nil {
   116  		log.Fatal(err)
   117  	}
   118  	path, _ := astutil.PathEnclosingInterval(af, tstart, tend)
   119  	var complit *ast.CompositeLit
   120  	for _, p := range path {
   121  		if p, ok := p.(*ast.CompositeLit); ok {
   122  			complit = p
   123  			break
   124  		}
   125  	}
   126  	if complit == nil {
   127  		log.Fatal("no composite literal found near point")
   128  	}
   129  	if len(complit.Elts) == 0 {
   130  		printComplit(complit, complit, lprog.Fset, lprog.Fset)
   131  		return
   132  	}
   133  	if _, ok := complit.Elts[0].(*ast.KeyValueExpr); ok {
   134  		lit := complit
   135  		if fOneLine {
   136  			lit = copyExpr(complit, 1).(*ast.CompositeLit)
   137  		}
   138  		printComplit(complit, lit, lprog.Fset, lprog.Fset)
   139  		return
   140  	}
   141  	_, ok := pkg.TypeOf(complit).Underlying().(*types.Struct)
   142  	if !ok {
   143  		log.Fatal("not a struct initialiser")
   144  		return
   145  	}
   146  
   147  	newComplit, lines := keyify(pkg, complit)
   148  	newFset := token.NewFileSet()
   149  	newFile := newFset.AddFile("", -1, lines)
   150  	for i := 1; i <= lines; i++ {
   151  		newFile.AddLine(i)
   152  	}
   153  	printComplit(complit, newComplit, lprog.Fset, newFset)
   154  }
   155  
   156  func keyify(
   157  	pkg *loader.PackageInfo,
   158  	complit *ast.CompositeLit,
   159  ) (*ast.CompositeLit, int) {
   160  	var calcPos func(int) token.Pos
   161  	if fOneLine {
   162  		calcPos = func(int) token.Pos { return token.Pos(1) }
   163  	} else {
   164  		calcPos = func(i int) token.Pos { return token.Pos(2 + i) }
   165  	}
   166  
   167  	st, _ := pkg.TypeOf(complit).Underlying().(*types.Struct)
   168  	newComplit := &ast.CompositeLit{
   169  		Type:   complit.Type,
   170  		Lbrace: 1,
   171  		Rbrace: token.Pos(st.NumFields() + 2),
   172  	}
   173  	if fOneLine {
   174  		newComplit.Rbrace = 1
   175  	}
   176  	numLines := 2 + st.NumFields()
   177  	n := 0
   178  	for i := 0; i < st.NumFields(); i++ {
   179  		field := st.Field(i)
   180  		val := complit.Elts[i]
   181  		if fRecursive {
   182  			if val2, ok := val.(*ast.CompositeLit); ok {
   183  				if _, ok := pkg.TypeOf(val2.Type).Underlying().(*types.Struct); ok {
   184  					var lines int
   185  					numLines += lines
   186  					val, lines = keyify(pkg, val2)
   187  				}
   188  			}
   189  		}
   190  		_, isIface := st.Field(i).Type().Underlying().(*types.Interface)
   191  		if fMinify && (isNil(val, pkg) || (!isIface && isZero(val, pkg))) {
   192  			continue
   193  		}
   194  		elt := &ast.KeyValueExpr{
   195  			Key:   &ast.Ident{NamePos: calcPos(n), Name: field.Name()},
   196  			Value: copyExpr(val, calcPos(n)),
   197  		}
   198  		newComplit.Elts = append(newComplit.Elts, elt)
   199  		n++
   200  	}
   201  	return newComplit, numLines
   202  }
   203  
   204  func isNil(val ast.Expr, pkg *loader.PackageInfo) bool {
   205  	ident, ok := val.(*ast.Ident)
   206  	if !ok {
   207  		return false
   208  	}
   209  	if _, ok := pkg.ObjectOf(ident).(*types.Nil); ok {
   210  		return true
   211  	}
   212  	if c, ok := pkg.ObjectOf(ident).(*types.Const); ok {
   213  		if c.Val().Kind() != constant.Bool {
   214  			return false
   215  		}
   216  		return !constant.BoolVal(c.Val())
   217  	}
   218  	return false
   219  }
   220  
   221  func isZero(val ast.Expr, pkg *loader.PackageInfo) bool {
   222  	switch val := val.(type) {
   223  	case *ast.BasicLit:
   224  		switch val.Value {
   225  		case `""`, "``", "0", "0.0", "0i", "0.":
   226  			return true
   227  		default:
   228  			return false
   229  		}
   230  	case *ast.Ident:
   231  		return isNil(val, pkg)
   232  	case *ast.CompositeLit:
   233  		typ := pkg.TypeOf(val.Type)
   234  		if typ == nil {
   235  			return false
   236  		}
   237  		isIface := false
   238  		switch typ := typ.Underlying().(type) {
   239  		case *types.Struct:
   240  		case *types.Array:
   241  			_, isIface = typ.Elem().Underlying().(*types.Interface)
   242  		default:
   243  			return false
   244  		}
   245  		for _, elt := range val.Elts {
   246  			if isNil(elt, pkg) || (!isIface && !isZero(elt, pkg)) {
   247  				return false
   248  			}
   249  		}
   250  		return true
   251  	}
   252  	return false
   253  }
   254  
   255  func printComplit(oldlit, newlit *ast.CompositeLit, oldfset, newfset *token.FileSet) {
   256  	buf := &bytes.Buffer{}
   257  	cfg := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
   258  	_ = cfg.Fprint(buf, newfset, newlit)
   259  	if fJSON {
   260  		output := struct {
   261  			Start       int    `json:"start"`
   262  			End         int    `json:"end"`
   263  			Replacement string `json:"replacement"`
   264  		}{
   265  			oldfset.Position(oldlit.Pos()).Offset,
   266  			oldfset.Position(oldlit.End()).Offset,
   267  			buf.String(),
   268  		}
   269  		_ = json.NewEncoder(os.Stdout).Encode(output)
   270  	} else {
   271  		fmt.Println(buf.String())
   272  	}
   273  }
   274  
   275  func copyExpr(expr ast.Expr, line token.Pos) ast.Expr {
   276  	switch expr := expr.(type) {
   277  	case *ast.BasicLit:
   278  		cp := *expr
   279  		cp.ValuePos = 0
   280  		return &cp
   281  	case *ast.BinaryExpr:
   282  		cp := *expr
   283  		cp.X = copyExpr(cp.X, line)
   284  		cp.OpPos = 0
   285  		cp.Y = copyExpr(cp.Y, line)
   286  		return &cp
   287  	case *ast.CallExpr:
   288  		cp := *expr
   289  		cp.Fun = copyExpr(cp.Fun, line)
   290  		cp.Lparen = 0
   291  		for i, v := range cp.Args {
   292  			cp.Args[i] = copyExpr(v, line)
   293  		}
   294  		if cp.Ellipsis != 0 {
   295  			cp.Ellipsis = line
   296  		}
   297  		cp.Rparen = 0
   298  		return &cp
   299  	case *ast.CompositeLit:
   300  		cp := *expr
   301  		cp.Type = copyExpr(cp.Type, line)
   302  		cp.Lbrace = 0
   303  		for i, v := range cp.Elts {
   304  			cp.Elts[i] = copyExpr(v, line)
   305  		}
   306  		cp.Rbrace = 0
   307  		return &cp
   308  	case *ast.Ident:
   309  		cp := *expr
   310  		cp.NamePos = 0
   311  		return &cp
   312  	case *ast.IndexExpr:
   313  		cp := *expr
   314  		cp.X = copyExpr(cp.X, line)
   315  		cp.Lbrack = 0
   316  		cp.Index = copyExpr(cp.Index, line)
   317  		cp.Rbrack = 0
   318  		return &cp
   319  	case *ast.KeyValueExpr:
   320  		cp := *expr
   321  		cp.Key = copyExpr(cp.Key, line)
   322  		cp.Colon = 0
   323  		cp.Value = copyExpr(cp.Value, line)
   324  		return &cp
   325  	case *ast.ParenExpr:
   326  		cp := *expr
   327  		cp.Lparen = 0
   328  		cp.X = copyExpr(cp.X, line)
   329  		cp.Rparen = 0
   330  		return &cp
   331  	case *ast.SelectorExpr:
   332  		cp := *expr
   333  		cp.X = copyExpr(cp.X, line)
   334  		cp.Sel = copyExpr(cp.Sel, line).(*ast.Ident)
   335  		return &cp
   336  	case *ast.SliceExpr:
   337  		cp := *expr
   338  		cp.X = copyExpr(cp.X, line)
   339  		cp.Lbrack = 0
   340  		cp.Low = copyExpr(cp.Low, line)
   341  		cp.High = copyExpr(cp.High, line)
   342  		cp.Max = copyExpr(cp.Max, line)
   343  		cp.Rbrack = 0
   344  		return &cp
   345  	case *ast.StarExpr:
   346  		cp := *expr
   347  		cp.Star = 0
   348  		cp.X = copyExpr(cp.X, line)
   349  		return &cp
   350  	case *ast.TypeAssertExpr:
   351  		cp := *expr
   352  		cp.X = copyExpr(cp.X, line)
   353  		cp.Lparen = 0
   354  		cp.Type = copyExpr(cp.Type, line)
   355  		cp.Rparen = 0
   356  		return &cp
   357  	case *ast.UnaryExpr:
   358  		cp := *expr
   359  		cp.OpPos = 0
   360  		cp.X = copyExpr(cp.X, line)
   361  		return &cp
   362  	case *ast.MapType:
   363  		cp := *expr
   364  		cp.Map = 0
   365  		cp.Key = copyExpr(cp.Key, line)
   366  		cp.Value = copyExpr(cp.Value, line)
   367  		return &cp
   368  	case *ast.ArrayType:
   369  		cp := *expr
   370  		cp.Lbrack = 0
   371  		cp.Len = copyExpr(cp.Len, line)
   372  		cp.Elt = copyExpr(cp.Elt, line)
   373  		return &cp
   374  	case *ast.Ellipsis:
   375  		cp := *expr
   376  		cp.Elt = copyExpr(cp.Elt, line)
   377  		cp.Ellipsis = line
   378  		return &cp
   379  	case *ast.InterfaceType:
   380  		cp := *expr
   381  		cp.Interface = 0
   382  		return &cp
   383  	case *ast.StructType:
   384  		cp := *expr
   385  		cp.Struct = 0
   386  		return &cp
   387  	case *ast.FuncLit:
   388  		return expr
   389  	case *ast.ChanType:
   390  		cp := *expr
   391  		cp.Arrow = 0
   392  		cp.Begin = 0
   393  		cp.Value = copyExpr(cp.Value, line)
   394  		return &cp
   395  	case nil:
   396  		return nil
   397  	default:
   398  		panic(fmt.Sprintf("shouldn't happen: unknown ast.Expr of type %T", expr))
   399  	}
   400  	return nil
   401  }