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 }