gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/github.com/opennota/check/cmd/structcheck/structcheck.go (about) 1 // structcheck 2 // This program is free software: you can redistribute it and/or modify 3 // it under the terms of the GNU General Public License as published by 4 // the Free Software Foundation, either version 3 of the License, or 5 // (at your option) any later version. 6 // 7 // This program is distributed in the hope that it will be useful, 8 // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 // GNU General Public License for more details. 11 // 12 // You should have received a copy of the GNU General Public License 13 // along with this program. If not, see <http://www.gnu.org/licenses/>. 14 15 package main 16 17 import ( 18 "flag" 19 "fmt" 20 "go/ast" 21 "go/build" 22 "go/types" 23 "os" 24 "strings" 25 26 "github.com/kisielk/gotool" 27 "golang.org/x/tools/go/loader" 28 ) 29 30 var ( 31 assignmentsOnly = flag.Bool("a", false, "Count assignments only") 32 loadTestFiles = flag.Bool("t", false, "Load test files too") 33 reportExported = flag.Bool("e", false, "Report exported fields") 34 buildTags = flag.String("tags", "", "Build tags") 35 ) 36 37 type visitor struct { 38 prog *loader.Program 39 pkg *loader.PackageInfo 40 m map[types.Type]map[string]int 41 skip map[types.Type]struct{} 42 } 43 44 func (v *visitor) decl(t types.Type, fieldName string) { 45 if _, ok := v.m[t]; !ok { 46 v.m[t] = make(map[string]int) 47 } 48 if _, ok := v.m[t][fieldName]; !ok { 49 v.m[t][fieldName] = 0 50 } 51 } 52 53 func (v *visitor) assignment(t types.Type, fieldName string) { 54 if _, ok := v.m[t]; !ok { 55 v.m[t] = make(map[string]int) 56 } 57 if _, ok := v.m[t][fieldName]; ok { 58 v.m[t][fieldName]++ 59 } else { 60 v.m[t][fieldName] = 1 61 } 62 } 63 64 func (v *visitor) typeSpec(node *ast.TypeSpec) { 65 if strukt, ok := node.Type.(*ast.StructType); ok { 66 t := v.pkg.Info.Defs[node.Name].Type() 67 for _, f := range strukt.Fields.List { 68 if len(f.Names) > 0 { 69 fieldName := f.Names[0].Name 70 v.decl(t, fieldName) 71 } 72 } 73 } 74 } 75 76 func (v *visitor) typeAndFieldName(expr *ast.SelectorExpr) (types.Type, string, bool) { 77 selection := v.pkg.Info.Selections[expr] 78 if selection == nil { 79 return nil, "", false 80 } 81 recv := selection.Recv() 82 if ptr, ok := recv.(*types.Pointer); ok { 83 recv = ptr.Elem() 84 } 85 return recv, selection.Obj().Name(), true 86 } 87 88 func (v *visitor) assignStmt(node *ast.AssignStmt) { 89 for _, lhs := range node.Lhs { 90 var selector *ast.SelectorExpr 91 switch expr := lhs.(type) { 92 case *ast.SelectorExpr: 93 selector = expr 94 case *ast.IndexExpr: 95 if expr, ok := expr.X.(*ast.SelectorExpr); ok { 96 selector = expr 97 } 98 } 99 if selector != nil { 100 if t, fn, ok := v.typeAndFieldName(selector); ok { 101 v.assignment(t, fn) 102 } 103 } 104 } 105 } 106 107 func (v *visitor) compositeLiteral(node *ast.CompositeLit) { 108 t := v.pkg.Info.Types[node.Type].Type 109 for _, expr := range node.Elts { 110 if kv, ok := expr.(*ast.KeyValueExpr); ok { 111 if ident, ok := kv.Key.(*ast.Ident); ok { 112 v.assignment(t, ident.Name) 113 } 114 } else { 115 // Struct literal with positional values. 116 // All the fields are assigned. 117 v.skip[t] = struct{}{} 118 break 119 } 120 } 121 } 122 123 func (v *visitor) Visit(node ast.Node) ast.Visitor { 124 switch node := node.(type) { 125 case *ast.TypeSpec: 126 v.typeSpec(node) 127 128 case *ast.AssignStmt: 129 if *assignmentsOnly { 130 v.assignStmt(node) 131 } 132 133 case *ast.SelectorExpr: 134 if !*assignmentsOnly { 135 if t, fn, ok := v.typeAndFieldName(node); ok { 136 v.assignment(t, fn) 137 } 138 } 139 140 case *ast.CompositeLit: 141 v.compositeLiteral(node) 142 } 143 144 return v 145 } 146 147 func main() { 148 flag.Parse() 149 exitStatus := 0 150 importPaths := gotool.ImportPaths(flag.Args()) 151 if len(importPaths) == 0 { 152 importPaths = []string{"."} 153 } 154 ctx := build.Default 155 if *buildTags != "" { 156 ctx.BuildTags = strings.Split(*buildTags, ",") 157 } 158 loadcfg := loader.Config{ 159 Build: &ctx, 160 } 161 rest, err := loadcfg.FromArgs(importPaths, *loadTestFiles) 162 if err != nil { 163 fmt.Fprintf(os.Stderr, "could not parse arguments: %s", err) 164 os.Exit(1) 165 } 166 if len(rest) > 0 { 167 fmt.Fprintf(os.Stderr, "unhandled extra arguments: %v", rest) 168 os.Exit(1) 169 } 170 171 program, err := loadcfg.Load() 172 if err != nil { 173 fmt.Fprintf(os.Stderr, "could not type check: %s", err) 174 os.Exit(1) 175 } 176 177 for _, pkg := range program.InitialPackages() { 178 visitor := &visitor{ 179 m: make(map[types.Type]map[string]int), 180 skip: make(map[types.Type]struct{}), 181 prog: program, 182 pkg: pkg, 183 } 184 for _, f := range pkg.Files { 185 ast.Walk(visitor, f) 186 } 187 188 for t := range visitor.m { 189 if _, skip := visitor.skip[t]; skip { 190 continue 191 } 192 for fieldName, v := range visitor.m[t] { 193 if !*reportExported && ast.IsExported(fieldName) { 194 continue 195 } 196 if v == 0 { 197 field, _, _ := types.LookupFieldOrMethod(t, false, pkg.Pkg, fieldName) 198 if field == nil { 199 fmt.Printf("%s: unknown field or method: %s.%s\n", pkg.Pkg.Path(), t, fieldName) 200 exitStatus = 1 201 continue 202 } 203 if fieldName == "XMLName" { 204 if named, ok := field.Type().(*types.Named); ok && named.Obj().Pkg().Path() == "encoding/xml" { 205 continue 206 } 207 } 208 pos := program.Fset.Position(field.Pos()) 209 fmt.Printf("%s: %s:%d:%d: %s.%s\n", 210 pkg.Pkg.Path(), pos.Filename, pos.Line, pos.Column, 211 types.TypeString(t, nil), fieldName, 212 ) 213 exitStatus = 1 214 } 215 } 216 } 217 } 218 os.Exit(exitStatus) 219 }