1 // keyify transforms unkeyed struct literals into a keyed ones.
19 "honnef.co/go/tools/lintcmd/version"
21 "golang.org/x/tools/go/ast/astutil"
22 "golang.org/x/tools/go/buildutil"
24 //lint:ignore SA1019 this tool is unmaintained, just keep it working
25 "golang.org/x/tools/go/loader"
38 flag.BoolVar(&fRecursive, "r", false, "keyify struct initializers recursively")
39 flag.BoolVar(&fOneLine, "o", false, "print new struct initializer on a single line")
40 flag.BoolVar(&fJSON, "json", false, "print new struct initializer as JSON")
41 flag.BoolVar(&fMinify, "m", false, "omit fields that are set to their zero value")
42 flag.BoolVar(&fModified, "modified", false, "read an archive of modified files from standard input")
43 flag.BoolVar(&fVersion, "version", false, "Print version and exit")
47 fmt.Printf("Usage: %s [flags] <position>\n\n", os.Args[0])
66 name, start, _, err := parsePos(pos)
70 eval, err := filepath.EvalSymlinks(name)
74 name, err = filepath.Abs(eval)
78 cwd, err := os.Getwd()
84 overlay, err := buildutil.ParseOverlayArchive(os.Stdin)
88 ctx = buildutil.OverlayContext(ctx, overlay)
90 bpkg, err := buildutil.ContainingPackage(ctx, cwd, name)
94 conf := &loader.Config{
97 conf.TypeCheckFuncBodies = func(s string) bool {
98 return s == bpkg.ImportPath || s == bpkg.ImportPath+"_test"
100 conf.ImportWithTests(bpkg.ImportPath)
101 lprog, err := conf.Load()
107 var pkg *loader.PackageInfo
109 for _, pkg = range lprog.InitialPackages() {
110 for _, ff := range pkg.Files {
111 file := lprog.Fset.File(ff.Pos())
112 if file.Name() == name {
120 log.Fatalf("couldn't find file %s", name)
122 tstart, tend, err := fileOffsetToPos(tf, start, start)
126 path, _ := astutil.PathEnclosingInterval(af, tstart, tend)
127 var complit *ast.CompositeLit
128 for _, p := range path {
129 if p, ok := p.(*ast.CompositeLit); ok {
135 log.Fatal("no composite literal found near point")
137 if len(complit.Elts) == 0 {
138 printComplit(complit, complit, lprog.Fset, lprog.Fset)
141 if _, ok := complit.Elts[0].(*ast.KeyValueExpr); ok {
144 lit = copyExpr(complit, 1).(*ast.CompositeLit)
146 printComplit(complit, lit, lprog.Fset, lprog.Fset)
149 _, ok := pkg.TypeOf(complit).Underlying().(*types.Struct)
151 log.Fatal("not a struct initialiser")
155 newComplit, lines := keyify(pkg, complit)
156 newFset := token.NewFileSet()
157 newFile := newFset.AddFile("", -1, lines)
158 for i := 1; i <= lines; i++ {
161 printComplit(complit, newComplit, lprog.Fset, newFset)
165 pkg *loader.PackageInfo,
166 complit *ast.CompositeLit,
167 ) (*ast.CompositeLit, int) {
168 var calcPos func(int) token.Pos
170 calcPos = func(int) token.Pos { return token.Pos(1) }
172 calcPos = func(i int) token.Pos { return token.Pos(2 + i) }
175 st, _ := pkg.TypeOf(complit).Underlying().(*types.Struct)
176 newComplit := &ast.CompositeLit{
179 Rbrace: token.Pos(st.NumFields() + 2),
182 newComplit.Rbrace = 1
184 numLines := 2 + st.NumFields()
186 for i := 0; i < st.NumFields(); i++ {
188 val := complit.Elts[i]
190 if val2, ok := val.(*ast.CompositeLit); ok {
191 if _, ok := pkg.TypeOf(val2.Type).Underlying().(*types.Struct); ok {
192 // FIXME(dh): this code is obviously wrong. But
193 // what were we intending to do here?
196 //lint:ignore SA4006 See FIXME above.
197 val, lines = keyify(pkg, val2)
201 _, isIface := st.Field(i).Type().Underlying().(*types.Interface)
202 if fMinify && (isNil(val, pkg) || (!isIface && isZero(val, pkg))) {
205 elt := &ast.KeyValueExpr{
206 Key: &ast.Ident{NamePos: calcPos(n), Name: field.Name()},
207 Value: copyExpr(val, calcPos(n)),
209 newComplit.Elts = append(newComplit.Elts, elt)
212 return newComplit, numLines
215 func isNil(val ast.Expr, pkg *loader.PackageInfo) bool {
216 ident, ok := val.(*ast.Ident)
220 if _, ok := pkg.ObjectOf(ident).(*types.Nil); ok {
223 if c, ok := pkg.ObjectOf(ident).(*types.Const); ok {
224 if c.Val().Kind() != constant.Bool {
227 return !constant.BoolVal(c.Val())
232 func isZero(val ast.Expr, pkg *loader.PackageInfo) bool {
233 switch val := val.(type) {
236 case `""`, "``", "0", "0.0", "0i", "0.":
242 return isNil(val, pkg)
243 case *ast.CompositeLit:
244 typ := pkg.TypeOf(val.Type)
249 switch typ := typ.Underlying().(type) {
252 _, isIface = typ.Elem().Underlying().(*types.Interface)
256 for _, elt := range val.Elts {
257 if isNil(elt, pkg) || (!isIface && !isZero(elt, pkg)) {
266 func printComplit(oldlit, newlit *ast.CompositeLit, oldfset, newfset *token.FileSet) {
267 buf := &bytes.Buffer{}
268 cfg := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
269 _ = cfg.Fprint(buf, newfset, newlit)
272 Start int `json:"start"`
274 Replacement string `json:"replacement"`
276 oldfset.Position(oldlit.Pos()).Offset,
277 oldfset.Position(oldlit.End()).Offset,
280 _ = json.NewEncoder(os.Stdout).Encode(output)
282 fmt.Println(buf.String())
286 func copyExpr(expr ast.Expr, line token.Pos) ast.Expr {
287 switch expr := expr.(type) {
292 case *ast.BinaryExpr:
294 cp.X = copyExpr(cp.X, line)
296 cp.Y = copyExpr(cp.Y, line)
300 cp.Fun = copyExpr(cp.Fun, line)
302 for i, v := range cp.Args {
303 cp.Args[i] = copyExpr(v, line)
305 if cp.Ellipsis != 0 {
310 case *ast.CompositeLit:
312 cp.Type = copyExpr(cp.Type, line)
314 for i, v := range cp.Elts {
315 cp.Elts[i] = copyExpr(v, line)
325 cp.X = copyExpr(cp.X, line)
327 cp.Index = copyExpr(cp.Index, line)
330 case *ast.KeyValueExpr:
332 cp.Key = copyExpr(cp.Key, line)
334 cp.Value = copyExpr(cp.Value, line)
339 cp.X = copyExpr(cp.X, line)
342 case *ast.SelectorExpr:
344 cp.X = copyExpr(cp.X, line)
345 cp.Sel = copyExpr(cp.Sel, line).(*ast.Ident)
349 cp.X = copyExpr(cp.X, line)
351 cp.Low = copyExpr(cp.Low, line)
352 cp.High = copyExpr(cp.High, line)
353 cp.Max = copyExpr(cp.Max, line)
359 cp.X = copyExpr(cp.X, line)
361 case *ast.TypeAssertExpr:
363 cp.X = copyExpr(cp.X, line)
365 cp.Type = copyExpr(cp.Type, line)
371 cp.X = copyExpr(cp.X, line)
376 cp.Key = copyExpr(cp.Key, line)
377 cp.Value = copyExpr(cp.Value, line)
382 cp.Len = copyExpr(cp.Len, line)
383 cp.Elt = copyExpr(cp.Elt, line)
387 cp.Elt = copyExpr(cp.Elt, line)
390 case *ast.InterfaceType:
394 case *ast.StructType:
404 cp.Value = copyExpr(cp.Value, line)
409 panic(fmt.Sprintf("shouldn't happen: unknown ast.Expr of type %T", expr))