├── .travis.yml ├── LICENSE ├── README.md ├── check ├── cache.go ├── check.go ├── interfacer_test.go ├── testdata │ ├── files │ │ ├── alias.go │ │ ├── assign.go │ │ ├── block.go │ │ ├── compare.go │ │ ├── composite.go │ │ ├── const.go │ │ ├── convert.go │ │ ├── dereference.go │ │ ├── fields.go │ │ ├── global_call.go │ │ ├── implement.go │ │ ├── import.go │ │ ├── issue22.go │ │ ├── lit_types.go │ │ ├── nested_func.go │ │ ├── noniface_usage.go │ │ ├── noninteresting.go │ │ ├── own_iface.go │ │ ├── param_groups.go │ │ ├── params.go │ │ ├── readme.go │ │ ├── recursive.go │ │ ├── repeated_types.go │ │ ├── results.go │ │ ├── shadow.go │ │ ├── simple.go │ │ ├── skip_call.go │ │ ├── unexported.go │ │ ├── unexported_byvalue.go │ │ ├── unusual_types.go │ │ ├── used.go │ │ ├── used_func.go │ │ └── wanted_type.go │ ├── local │ │ ├── multipkg │ │ │ ├── pkg2 │ │ │ │ └── simple.go │ │ │ └── simple.go │ │ └── single │ │ │ ├── simple.go │ │ │ └── struct.go │ └── src │ │ ├── deps │ │ ├── import.go │ │ ├── import15.go │ │ ├── single.go │ │ └── vendor │ │ │ └── foo │ │ │ └── bar │ │ │ └── def.go │ │ ├── grab-import │ │ ├── def │ │ │ ├── def.go │ │ │ └── nested │ │ │ │ └── def.go │ │ └── use.go │ │ ├── nested │ │ └── pkg │ │ │ └── simple.go │ │ ├── single │ │ ├── simple.go │ │ └── struct.go │ │ └── skip │ │ ├── .hidden │ │ └── simple.go │ │ ├── simple.go │ │ ├── testdata │ │ └── simple.go │ │ └── vendor │ │ └── simple.go └── types.go └── main.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.9.x 5 | - 1.10.x 6 | 7 | go_import_path: mvdan.cc/interfacer 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Daniel Martí. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of the copyright holder nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # interfacer 2 | 3 | [![GoDoc](https://godoc.org/mvdan.cc/interfacer?status.svg)](https://godoc.org/mvdan.cc/interfacer) 4 | [![Build Status](https://travis-ci.org/mvdan/interfacer.svg?branch=master)](https://travis-ci.org/mvdan/interfacer) 5 | 6 | **Deprecated**: A tool that suggests interfaces is prone to bad suggestions, so 7 | its usefulness in real code is limited. This tool will remain available as a 8 | proof of concept, and for others to examine and learn from. 9 | 10 | A linter that suggests interface types. In other words, it warns about 11 | the usage of types that are more specific than necessary. 12 | 13 | go get -u mvdan.cc/interfacer 14 | 15 | Note that this linter's suggestions tend to be subjective, as interfaces 16 | are not always the better option. You should select the proposed changes 17 | that make sense in your codebase, instead of following all of them 18 | blindly. 19 | 20 | ### Usage 21 | 22 | ```go 23 | func ProcessInput(f *os.File) error { 24 | b, err := ioutil.ReadAll(f) 25 | if err != nil { 26 | return err 27 | } 28 | return processBytes(b) 29 | } 30 | ``` 31 | 32 | ```sh 33 | $ interfacer ./... 34 | foo.go:10:19: f can be io.Reader 35 | ``` 36 | 37 | ### Basic idea 38 | 39 | This tool inspects the parameters of your functions to see if they fit 40 | an interface type that is less specific than the current type. 41 | 42 | The example above illustrates this point. Overly specific interfaces 43 | also trigger a warning - if `f` were an `io.ReadCloser`, the same 44 | message would appear. 45 | 46 | It suggests interface types defined both in the func's package and the 47 | package's imports (two levels; direct imports and their direct imports). 48 | 49 | ### False positives 50 | 51 | To avoid false positives, it never does any suggestions on functions 52 | that may be implementing an interface method or a named function type. 53 | 54 | It also skips parameters passed by value (excluding pointers and 55 | interfaces) on unexported functions, since that would introduce extra 56 | allocations where they are usually not worth the tradeoff. 57 | 58 | ### Suppressing warnings 59 | 60 | If a suggestion is technically correct but doesn't make sense, you can 61 | still suppress the warning by mentioning the type in the function name: 62 | 63 | ```go 64 | func ProcessInputFile(f *os.File) error { 65 | // use as an io.Reader 66 | } 67 | ``` 68 | -------------------------------------------------------------------------------- /check/cache.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Daniel Martí 2 | // See LICENSE for licensing information 3 | 4 | package check 5 | 6 | import ( 7 | "go/ast" 8 | "go/types" 9 | ) 10 | 11 | type pkgTypes struct { 12 | ifaces map[string]string 13 | funcSigns map[string]bool 14 | } 15 | 16 | func (p *pkgTypes) getTypes(pkg *types.Package) { 17 | p.ifaces = make(map[string]string) 18 | p.funcSigns = make(map[string]bool) 19 | done := make(map[*types.Package]bool) 20 | addTypes := func(pkg *types.Package, top bool) { 21 | if done[pkg] { 22 | return 23 | } 24 | done[pkg] = true 25 | ifs, funs := fromScope(pkg.Scope()) 26 | fullName := func(name string) string { 27 | if !top { 28 | return pkg.Path() + "." + name 29 | } 30 | return name 31 | } 32 | for iftype, name := range ifs { 33 | // only suggest exported interfaces 34 | if ast.IsExported(name) { 35 | p.ifaces[iftype] = fullName(name) 36 | } 37 | } 38 | for ftype := range funs { 39 | // ignore non-exported func signatures too 40 | p.funcSigns[ftype] = true 41 | } 42 | } 43 | for _, imp := range pkg.Imports() { 44 | addTypes(imp, false) 45 | for _, imp2 := range imp.Imports() { 46 | addTypes(imp2, false) 47 | } 48 | } 49 | addTypes(pkg, true) 50 | } 51 | -------------------------------------------------------------------------------- /check/check.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Daniel Martí 2 | // See LICENSE for licensing information 3 | 4 | package check // import "mvdan.cc/interfacer/check" 5 | 6 | import ( 7 | "fmt" 8 | "go/ast" 9 | "go/token" 10 | "go/types" 11 | "os" 12 | "strings" 13 | 14 | "golang.org/x/tools/go/loader" 15 | "golang.org/x/tools/go/ssa" 16 | "golang.org/x/tools/go/ssa/ssautil" 17 | 18 | "github.com/kisielk/gotool" 19 | "mvdan.cc/lint" 20 | ) 21 | 22 | func toDiscard(usage *varUsage) bool { 23 | if usage.discard { 24 | return true 25 | } 26 | for to := range usage.assigned { 27 | if toDiscard(to) { 28 | return true 29 | } 30 | } 31 | return false 32 | } 33 | 34 | func allCalls(usage *varUsage, all, ftypes map[string]string) { 35 | for fname := range usage.calls { 36 | all[fname] = ftypes[fname] 37 | } 38 | for to := range usage.assigned { 39 | allCalls(to, all, ftypes) 40 | } 41 | } 42 | 43 | func (c *Checker) interfaceMatching(param *types.Var, usage *varUsage) (string, string) { 44 | if toDiscard(usage) { 45 | return "", "" 46 | } 47 | ftypes := typeFuncMap(param.Type()) 48 | called := make(map[string]string, len(usage.calls)) 49 | allCalls(usage, called, ftypes) 50 | s := funcMapString(called) 51 | return c.ifaces[s], s 52 | } 53 | 54 | type varUsage struct { 55 | calls map[string]struct{} 56 | discard bool 57 | 58 | assigned map[*varUsage]struct{} 59 | } 60 | 61 | type funcDecl struct { 62 | astDecl *ast.FuncDecl 63 | ssaFn *ssa.Function 64 | } 65 | 66 | // CheckArgs checks the packages specified by their import paths in 67 | // args. 68 | func CheckArgs(args []string) ([]string, error) { 69 | paths := gotool.ImportPaths(args) 70 | conf := loader.Config{} 71 | conf.AllowErrors = true 72 | rest, err := conf.FromArgs(paths, false) 73 | if err != nil { 74 | return nil, err 75 | } 76 | if len(rest) > 0 { 77 | return nil, fmt.Errorf("unwanted extra args: %v", rest) 78 | } 79 | lprog, err := conf.Load() 80 | if err != nil { 81 | return nil, err 82 | } 83 | prog := ssautil.CreateProgram(lprog, 0) 84 | prog.Build() 85 | c := new(Checker) 86 | c.Program(lprog) 87 | c.ProgramSSA(prog) 88 | issues, err := c.Check() 89 | if err != nil { 90 | return nil, err 91 | } 92 | wd, err := os.Getwd() 93 | if err != nil { 94 | return nil, err 95 | } 96 | lines := make([]string, len(issues)) 97 | for i, issue := range issues { 98 | fpos := prog.Fset.Position(issue.Pos()).String() 99 | if strings.HasPrefix(fpos, wd) { 100 | fpos = fpos[len(wd)+1:] 101 | } 102 | lines[i] = fmt.Sprintf("%s: %s", fpos, issue.Message()) 103 | } 104 | return lines, nil 105 | } 106 | 107 | type Checker struct { 108 | lprog *loader.Program 109 | prog *ssa.Program 110 | 111 | pkgTypes 112 | *loader.PackageInfo 113 | 114 | funcs []*funcDecl 115 | 116 | ssaByPos map[token.Pos]*ssa.Function 117 | 118 | discardFuncs map[*types.Signature]struct{} 119 | 120 | vars map[*types.Var]*varUsage 121 | } 122 | 123 | var ( 124 | _ lint.Checker = (*Checker)(nil) 125 | _ lint.WithSSA = (*Checker)(nil) 126 | ) 127 | 128 | func (c *Checker) Program(lprog *loader.Program) { 129 | c.lprog = lprog 130 | } 131 | 132 | func (c *Checker) ProgramSSA(prog *ssa.Program) { 133 | c.prog = prog 134 | } 135 | 136 | func (c *Checker) Check() ([]lint.Issue, error) { 137 | var total []lint.Issue 138 | c.ssaByPos = make(map[token.Pos]*ssa.Function) 139 | wantPkg := make(map[*types.Package]bool) 140 | for _, pinfo := range c.lprog.InitialPackages() { 141 | wantPkg[pinfo.Pkg] = true 142 | } 143 | for fn := range ssautil.AllFunctions(c.prog) { 144 | if fn.Pkg == nil { // builtin? 145 | continue 146 | } 147 | if len(fn.Blocks) == 0 { // stub 148 | continue 149 | } 150 | if !wantPkg[fn.Pkg.Pkg] { // not part of given pkgs 151 | continue 152 | } 153 | c.ssaByPos[fn.Pos()] = fn 154 | } 155 | for _, pinfo := range c.lprog.InitialPackages() { 156 | pkg := pinfo.Pkg 157 | c.getTypes(pkg) 158 | c.PackageInfo = c.lprog.AllPackages[pkg] 159 | total = append(total, c.checkPkg()...) 160 | } 161 | return total, nil 162 | } 163 | 164 | func (c *Checker) checkPkg() []lint.Issue { 165 | c.discardFuncs = make(map[*types.Signature]struct{}) 166 | c.vars = make(map[*types.Var]*varUsage) 167 | c.funcs = c.funcs[:0] 168 | findFuncs := func(node ast.Node) bool { 169 | decl, ok := node.(*ast.FuncDecl) 170 | if !ok { 171 | return true 172 | } 173 | ssaFn := c.ssaByPos[decl.Name.Pos()] 174 | if ssaFn == nil { 175 | return true 176 | } 177 | fd := &funcDecl{ 178 | astDecl: decl, 179 | ssaFn: ssaFn, 180 | } 181 | if c.funcSigns[signString(fd.ssaFn.Signature)] { 182 | // implements interface 183 | return true 184 | } 185 | c.funcs = append(c.funcs, fd) 186 | ast.Walk(c, decl.Body) 187 | return true 188 | } 189 | for _, f := range c.Files { 190 | ast.Inspect(f, findFuncs) 191 | } 192 | return c.packageIssues() 193 | } 194 | 195 | func paramVarAndType(sign *types.Signature, i int) (*types.Var, types.Type) { 196 | params := sign.Params() 197 | extra := sign.Variadic() && i >= params.Len()-1 198 | if !extra { 199 | if i >= params.Len() { 200 | // builtins with multiple signatures 201 | return nil, nil 202 | } 203 | vr := params.At(i) 204 | return vr, vr.Type() 205 | } 206 | last := params.At(params.Len() - 1) 207 | switch x := last.Type().(type) { 208 | case *types.Slice: 209 | return nil, x.Elem() 210 | default: 211 | return nil, x 212 | } 213 | } 214 | 215 | func (c *Checker) varUsage(e ast.Expr) *varUsage { 216 | id, ok := e.(*ast.Ident) 217 | if !ok { 218 | return nil 219 | } 220 | param, ok := c.ObjectOf(id).(*types.Var) 221 | if !ok { 222 | // not a variable 223 | return nil 224 | } 225 | if usage, e := c.vars[param]; e { 226 | return usage 227 | } 228 | if !interesting(param.Type()) { 229 | return nil 230 | } 231 | usage := &varUsage{ 232 | calls: make(map[string]struct{}), 233 | assigned: make(map[*varUsage]struct{}), 234 | } 235 | c.vars[param] = usage 236 | return usage 237 | } 238 | 239 | func (c *Checker) addUsed(e ast.Expr, as types.Type) { 240 | if as == nil { 241 | return 242 | } 243 | if usage := c.varUsage(e); usage != nil { 244 | // using variable 245 | iface, ok := as.Underlying().(*types.Interface) 246 | if !ok { 247 | usage.discard = true 248 | return 249 | } 250 | for i := 0; i < iface.NumMethods(); i++ { 251 | m := iface.Method(i) 252 | usage.calls[m.Name()] = struct{}{} 253 | } 254 | } else if t, ok := c.TypeOf(e).(*types.Signature); ok { 255 | // using func 256 | c.discardFuncs[t] = struct{}{} 257 | } 258 | } 259 | 260 | func (c *Checker) addAssign(to, from ast.Expr) { 261 | pto := c.varUsage(to) 262 | pfrom := c.varUsage(from) 263 | if pto == nil || pfrom == nil { 264 | // either isn't interesting 265 | return 266 | } 267 | pfrom.assigned[pto] = struct{}{} 268 | } 269 | 270 | func (c *Checker) discard(e ast.Expr) { 271 | if usage := c.varUsage(e); usage != nil { 272 | usage.discard = true 273 | } 274 | } 275 | 276 | func (c *Checker) comparedWith(e, with ast.Expr) { 277 | if _, ok := with.(*ast.BasicLit); ok { 278 | c.discard(e) 279 | } 280 | } 281 | 282 | func (c *Checker) Visit(node ast.Node) ast.Visitor { 283 | switch x := node.(type) { 284 | case *ast.SelectorExpr: 285 | if _, ok := c.TypeOf(x.Sel).(*types.Signature); !ok { 286 | c.discard(x.X) 287 | } 288 | case *ast.StarExpr: 289 | c.discard(x.X) 290 | case *ast.UnaryExpr: 291 | c.discard(x.X) 292 | case *ast.IndexExpr: 293 | c.discard(x.X) 294 | case *ast.IncDecStmt: 295 | c.discard(x.X) 296 | case *ast.BinaryExpr: 297 | switch x.Op { 298 | case token.EQL, token.NEQ: 299 | c.comparedWith(x.X, x.Y) 300 | c.comparedWith(x.Y, x.X) 301 | default: 302 | c.discard(x.X) 303 | c.discard(x.Y) 304 | } 305 | case *ast.ValueSpec: 306 | for _, val := range x.Values { 307 | c.addUsed(val, c.TypeOf(x.Type)) 308 | } 309 | case *ast.AssignStmt: 310 | for i, val := range x.Rhs { 311 | left := x.Lhs[i] 312 | if x.Tok == token.ASSIGN { 313 | c.addUsed(val, c.TypeOf(left)) 314 | } 315 | c.addAssign(left, val) 316 | } 317 | case *ast.CompositeLit: 318 | for i, e := range x.Elts { 319 | switch y := e.(type) { 320 | case *ast.KeyValueExpr: 321 | c.addUsed(y.Key, c.TypeOf(y.Value)) 322 | c.addUsed(y.Value, c.TypeOf(y.Key)) 323 | case *ast.Ident: 324 | c.addUsed(y, compositeIdentType(c.TypeOf(x), i)) 325 | } 326 | } 327 | case *ast.CallExpr: 328 | switch y := c.TypeOf(x.Fun).Underlying().(type) { 329 | case *types.Signature: 330 | c.onMethodCall(x, y) 331 | default: 332 | // type conversion 333 | if len(x.Args) == 1 { 334 | c.addUsed(x.Args[0], y) 335 | } 336 | } 337 | } 338 | return c 339 | } 340 | 341 | func compositeIdentType(t types.Type, i int) types.Type { 342 | switch x := t.(type) { 343 | case *types.Named: 344 | return compositeIdentType(x.Underlying(), i) 345 | case *types.Struct: 346 | return x.Field(i).Type() 347 | case *types.Array: 348 | return x.Elem() 349 | case *types.Slice: 350 | return x.Elem() 351 | } 352 | return nil 353 | } 354 | 355 | func (c *Checker) onMethodCall(ce *ast.CallExpr, sign *types.Signature) { 356 | for i, e := range ce.Args { 357 | paramObj, t := paramVarAndType(sign, i) 358 | // Don't if this is a parameter being re-used as itself 359 | // in a recursive call 360 | if id, ok := e.(*ast.Ident); ok { 361 | if paramObj == c.ObjectOf(id) { 362 | continue 363 | } 364 | } 365 | c.addUsed(e, t) 366 | } 367 | sel, ok := ce.Fun.(*ast.SelectorExpr) 368 | if !ok { 369 | return 370 | } 371 | // receiver func call on the left side 372 | if usage := c.varUsage(sel.X); usage != nil { 373 | usage.calls[sel.Sel.Name] = struct{}{} 374 | } 375 | } 376 | 377 | func (fd *funcDecl) paramGroups() [][]*types.Var { 378 | astList := fd.astDecl.Type.Params.List 379 | groups := make([][]*types.Var, len(astList)) 380 | signIndex := 0 381 | for i, field := range astList { 382 | group := make([]*types.Var, len(field.Names)) 383 | for j := range field.Names { 384 | group[j] = fd.ssaFn.Signature.Params().At(signIndex) 385 | signIndex++ 386 | } 387 | groups[i] = group 388 | } 389 | return groups 390 | } 391 | 392 | func (c *Checker) packageIssues() []lint.Issue { 393 | var issues []lint.Issue 394 | for _, fd := range c.funcs { 395 | if _, e := c.discardFuncs[fd.ssaFn.Signature]; e { 396 | continue 397 | } 398 | for _, group := range fd.paramGroups() { 399 | issues = append(issues, c.groupIssues(fd, group)...) 400 | } 401 | } 402 | return issues 403 | } 404 | 405 | type Issue struct { 406 | pos token.Pos 407 | msg string 408 | } 409 | 410 | func (i Issue) Pos() token.Pos { return i.pos } 411 | func (i Issue) Message() string { return i.msg } 412 | 413 | func (c *Checker) groupIssues(fd *funcDecl, group []*types.Var) []lint.Issue { 414 | var issues []lint.Issue 415 | for _, param := range group { 416 | usage := c.vars[param] 417 | if usage == nil { 418 | return nil 419 | } 420 | newType := c.paramNewType(fd.astDecl.Name.Name, param, usage) 421 | if newType == "" { 422 | return nil 423 | } 424 | issues = append(issues, Issue{ 425 | pos: param.Pos(), 426 | msg: fmt.Sprintf("%s can be %s", param.Name(), newType), 427 | }) 428 | } 429 | return issues 430 | } 431 | 432 | func willAddAllocation(t types.Type) bool { 433 | switch t.Underlying().(type) { 434 | case *types.Pointer, *types.Interface: 435 | return false 436 | } 437 | return true 438 | } 439 | 440 | func (c *Checker) paramNewType(funcName string, param *types.Var, usage *varUsage) string { 441 | t := param.Type() 442 | if !ast.IsExported(funcName) && willAddAllocation(t) { 443 | return "" 444 | } 445 | if named := typeNamed(t); named != nil { 446 | tname := named.Obj().Name() 447 | vname := param.Name() 448 | if mentionsName(funcName, tname) || mentionsName(funcName, vname) { 449 | return "" 450 | } 451 | } 452 | ifname, iftype := c.interfaceMatching(param, usage) 453 | if ifname == "" { 454 | return "" 455 | } 456 | if types.IsInterface(t.Underlying()) { 457 | if have := funcMapString(typeFuncMap(t)); have == iftype { 458 | return "" 459 | } 460 | } 461 | return ifname 462 | } 463 | -------------------------------------------------------------------------------- /check/interfacer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Daniel Martí 2 | // See LICENSE for licensing information 3 | 4 | package check 5 | 6 | import ( 7 | "fmt" 8 | "go/ast" 9 | "go/build" 10 | "go/parser" 11 | "go/token" 12 | "io/ioutil" 13 | "os" 14 | "path/filepath" 15 | "reflect" 16 | "regexp" 17 | "strings" 18 | "testing" 19 | 20 | "github.com/kisielk/gotool" 21 | ) 22 | 23 | const testdata = "testdata" 24 | 25 | var ( 26 | issuesRe = regexp.MustCompile(`^WARN (.*)\n?$`) 27 | singleRe = regexp.MustCompile(`([^ ]*) can be ([^ ]*)(,|$)`) 28 | ) 29 | 30 | func goFiles(t *testing.T, p string) []string { 31 | if strings.HasSuffix(p, ".go") { 32 | return []string{p} 33 | } 34 | dirs := gotool.ImportPaths([]string{p}) 35 | var paths []string 36 | for _, dir := range dirs { 37 | files, err := ioutil.ReadDir(dir) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | for _, file := range files { 42 | if file.IsDir() { 43 | continue 44 | } 45 | paths = append(paths, filepath.Join(dir, file.Name())) 46 | } 47 | } 48 | return paths 49 | } 50 | 51 | type identVisitor struct { 52 | fset *token.FileSet 53 | idents map[string]token.Pos 54 | } 55 | 56 | func identKey(line int, name string) string { 57 | return fmt.Sprintf("%d %s", line, name) 58 | } 59 | 60 | func (v *identVisitor) Visit(n ast.Node) ast.Visitor { 61 | switch x := n.(type) { 62 | case *ast.Ident: 63 | line := v.fset.Position(x.Pos()).Line 64 | v.idents[identKey(line, x.Name)] = x.Pos() 65 | } 66 | return v 67 | } 68 | 69 | func identPositions(fset *token.FileSet, f *ast.File) map[string]token.Pos { 70 | v := &identVisitor{ 71 | fset: fset, 72 | idents: make(map[string]token.Pos), 73 | } 74 | ast.Walk(v, f) 75 | return v.idents 76 | } 77 | 78 | func wantedIssues(t *testing.T, p string) []string { 79 | fset := token.NewFileSet() 80 | lines := make([]string, 0) 81 | for _, path := range goFiles(t, p) { 82 | src, err := os.Open(path) 83 | if err != nil { 84 | t.Fatal(err) 85 | } 86 | f, err := parser.ParseFile(fset, path, src, parser.ParseComments) 87 | src.Close() 88 | if err != nil { 89 | t.Fatal(err) 90 | } 91 | identPos := identPositions(fset, f) 92 | for _, group := range f.Comments { 93 | cm := issuesRe.FindStringSubmatch(group.Text()) 94 | if cm == nil { 95 | continue 96 | } 97 | for _, m := range singleRe.FindAllStringSubmatch(cm[1], -1) { 98 | vname, tname := m[1], m[2] 99 | line := fset.Position(group.Pos()).Line 100 | pos := fset.Position(identPos[identKey(line, vname)]) 101 | lines = append(lines, fmt.Sprintf("%s: %s can be %s", 102 | pos, vname, tname)) 103 | } 104 | } 105 | } 106 | return lines 107 | } 108 | 109 | func doTest(t *testing.T, p string) { 110 | t.Run(p, func(t *testing.T) { 111 | lines := wantedIssues(t, p) 112 | doTestLines(t, p, lines, p) 113 | }) 114 | } 115 | 116 | func doTestLines(t *testing.T, name string, want []string, args ...string) { 117 | got, err := CheckArgs(args) 118 | if err != nil { 119 | t.Fatalf("Did not want error in %s:\n%v", name, err) 120 | } 121 | if !reflect.DeepEqual(want, got) { 122 | t.Fatalf("Output mismatch in %s:\nwant:\n%s\ngot:\n%s", 123 | name, strings.Join(want, "\n"), strings.Join(got, "\n")) 124 | } 125 | } 126 | 127 | func doTestString(t *testing.T, name, want string, args ...string) { 128 | switch len(args) { 129 | case 0: 130 | args = []string{name} 131 | case 1: 132 | if args[0] == "" { 133 | args = nil 134 | } 135 | } 136 | issues, err := CheckArgs(args) 137 | if err != nil { 138 | t.Fatalf("Did not want error in %s:\n%v", name, err) 139 | } 140 | got := strings.Join(issues, "\n") 141 | if want != got { 142 | t.Fatalf("Output mismatch in %s:\nExpected:\n%s\nGot:\n%s", 143 | name, want, got) 144 | } 145 | } 146 | 147 | func inputPaths(t *testing.T) []string { 148 | all, err := filepath.Glob("*") 149 | if err != nil { 150 | t.Fatal(err) 151 | } 152 | return all 153 | } 154 | 155 | func chdirUndo(t *testing.T, d string) func() { 156 | wd, err := os.Getwd() 157 | if err != nil { 158 | t.Fatal(err) 159 | } 160 | if err := os.Chdir(d); err != nil { 161 | t.Fatal(err) 162 | } 163 | return func() { 164 | if err := os.Chdir(wd); err != nil { 165 | t.Fatal(err) 166 | } 167 | } 168 | } 169 | 170 | func runFileTests(t *testing.T, paths ...string) { 171 | defer chdirUndo(t, "files")() 172 | if len(paths) == 0 { 173 | paths = inputPaths(t) 174 | } 175 | for _, p := range paths { 176 | doTest(t, p) 177 | } 178 | } 179 | 180 | func runLocalTests(t *testing.T, paths ...string) { 181 | defer chdirUndo(t, "local")() 182 | if len(paths) > 0 { 183 | for _, p := range paths { 184 | doTest(t, p) 185 | } 186 | return 187 | } 188 | for _, p := range inputPaths(t) { 189 | paths = append(paths, "./"+p+"/...") 190 | } 191 | for _, p := range paths { 192 | doTest(t, p) 193 | } 194 | // non-recursive 195 | doTest(t, "./single") 196 | doTestString(t, "no-args", "", "") 197 | } 198 | 199 | func runNonlocalTests(t *testing.T, paths ...string) { 200 | // std 201 | doTestString(t, "std-pkg", "", "sync/atomic") 202 | defer chdirUndo(t, "src")() 203 | if len(paths) > 0 { 204 | for _, p := range paths { 205 | doTest(t, p) 206 | } 207 | return 208 | } 209 | for _, p := range inputPaths(t) { 210 | doTest(t, p+"/...") 211 | } 212 | // local recursive 213 | doTest(t, "./nested/...") 214 | // non-recursive 215 | doTest(t, "single") 216 | // make sure we don't miss a package's imports 217 | doTestString(t, "grab-import", "grab-import/use.go:27:15: s can be grab-import/def/nested.Fooer") 218 | defer chdirUndo(t, "nested/pkg")() 219 | // relative paths 220 | doTestString(t, "rel-path", "simple.go:12:17: rc can be Closer", "./...") 221 | } 222 | 223 | func TestMain(m *testing.M) { 224 | if err := os.Chdir(testdata); err != nil { 225 | panic(err) 226 | } 227 | wd, err := os.Getwd() 228 | if err != nil { 229 | panic(err) 230 | } 231 | build.Default.GOPATH = wd 232 | gotool.DefaultContext.BuildContext.GOPATH = wd 233 | os.Exit(m.Run()) 234 | } 235 | 236 | func TestIssues(t *testing.T) { 237 | runFileTests(t) 238 | runLocalTests(t) 239 | runNonlocalTests(t) 240 | } 241 | 242 | func TestExtraArg(t *testing.T) { 243 | _, err := CheckArgs([]string{"single", "--", "foo", "bar"}) 244 | got := err.Error() 245 | want := "unwanted extra args: [foo bar]" 246 | if got != want { 247 | t.Fatalf("Error mismatch:\nExpected:\n%s\nGot:\n%s", want, got) 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /check/testdata/files/alias.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type MyCloser interface { 4 | Close() error 5 | } 6 | 7 | func Foo(c MyCloser) { 8 | c.Close() 9 | } 10 | -------------------------------------------------------------------------------- /check/testdata/files/assign.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Closer interface { 4 | Close() 5 | } 6 | 7 | type ReadCloser interface { 8 | Closer 9 | Read() 10 | } 11 | 12 | func Transitive(rc ReadCloser) { // WARN rc can be Closer 13 | a := rc 14 | b := a 15 | c := b 16 | c.Close() 17 | } 18 | -------------------------------------------------------------------------------- /check/testdata/files/block.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Closer interface { 4 | Close() 5 | } 6 | 7 | type ReadCloser interface { 8 | Closer 9 | Read() 10 | } 11 | 12 | func ForIf(rc ReadCloser) { 13 | for i := 0; i < 10; i++ { 14 | if i%2 == 0 { 15 | rc.Close() 16 | } 17 | } 18 | rc.Read() 19 | } 20 | 21 | func IfWrong(rc ReadCloser) { // WARN rc can be Closer 22 | if 3 > 2 { 23 | rc.Close() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /check/testdata/files/compare.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Reader interface { 4 | Read([]byte) (int, error) 5 | } 6 | 7 | type Closer interface { 8 | Close() error 9 | } 10 | 11 | type ReadCloser interface { 12 | Reader 13 | Closer 14 | } 15 | 16 | func CompareNil(rc ReadCloser) { // WARN rc can be Closer 17 | if rc != nil { 18 | rc.Close() 19 | } 20 | } 21 | 22 | func CompareIface(rc ReadCloser) { // WARN rc can be Closer 23 | if rc != ReadCloser(nil) { 24 | rc.Close() 25 | } 26 | } 27 | 28 | func CompareIfaceDiff(rc ReadCloser) { // WARN rc can be Closer 29 | if rc != Reader(nil) { 30 | rc.Close() 31 | } 32 | } 33 | 34 | type mint int 35 | 36 | func (m mint) Close() error { 37 | return nil 38 | } 39 | 40 | func CompareStruct(m mint) { // WARN m can be Closer 41 | if m != mint(3) { 42 | m.Close() 43 | } 44 | } 45 | 46 | func CompareStructVar(m mint) { // WARN m can be Closer 47 | m2 := mint(2) 48 | if m == m2 { 49 | m.Close() 50 | } 51 | } 52 | 53 | func CompareLit(m mint) { 54 | if m != 3 { 55 | m.Close() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /check/testdata/files/composite.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type mint int 4 | 5 | func (m mint) Close() error { 6 | return nil 7 | } 8 | 9 | func MapKey(m mint) { 10 | m.Close() 11 | _ = map[mint]string{ 12 | m: "foo", 13 | } 14 | } 15 | 16 | func MapValue(m mint) { 17 | m.Close() 18 | _ = map[string]mint{ 19 | "foo": m, 20 | } 21 | } 22 | 23 | type Fooer interface { 24 | Foo() 25 | } 26 | 27 | type FooBarer interface { 28 | Fooer 29 | Bar() 30 | } 31 | 32 | type holdFooer struct { 33 | f Fooer 34 | } 35 | 36 | type holdFooBarer struct { 37 | fb FooBarer 38 | } 39 | 40 | func Correct(fb FooBarer) { 41 | _ = holdFooBarer{fb: fb} 42 | } 43 | 44 | func CorrectNoKey(fb FooBarer) { 45 | _ = holdFooBarer{fb} 46 | } 47 | 48 | func Wrong(fb FooBarer) { // WARN fb can be Fooer 49 | _ = holdFooer{f: fb} 50 | } 51 | 52 | func WrongNoKey(fb FooBarer) { // WARN fb can be Fooer 53 | _ = holdFooer{fb} 54 | } 55 | 56 | func WrongNoKeyInplace(fb FooBarer) { // WARN fb can be Fooer 57 | _ = struct { 58 | f Fooer 59 | }{fb} 60 | } 61 | 62 | func WrongNoKeyMultiple(fb FooBarer) { // WARN fb can be Fooer 63 | _ = struct { 64 | f Fooer 65 | s string 66 | }{fb, "bar"} 67 | } 68 | 69 | type holdFooerNested holdFooer 70 | 71 | func WrongNoKeyDeep(fb FooBarer) { // WARN fb can be Fooer 72 | _ = holdFooerNested{fb} 73 | } 74 | 75 | func WrongNoKeyArray(fb FooBarer) { // WARN fb can be Fooer 76 | _ = [...]Fooer{fb} 77 | } 78 | 79 | func WrongNoKeySlice(fb FooBarer) { // WARN fb can be Fooer 80 | _ = []Fooer{fb} 81 | } 82 | 83 | func WrongWalkValue(fb FooBarer) { // WARN fb can be Fooer 84 | fn := func(f Fooer) Fooer { return f } 85 | _ = []Fooer{fn(fb)} 86 | } 87 | -------------------------------------------------------------------------------- /check/testdata/files/const.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Reader interface { 4 | Read(p []byte) (n int, err error) 5 | } 6 | 7 | type Seeker interface { 8 | Seek(int64, int) (int64, error) 9 | } 10 | 11 | type ReadSeeker interface { 12 | Reader 13 | Seeker 14 | } 15 | 16 | const offset = 1 17 | 18 | func Const(s Seeker) { 19 | var whence int = 0 20 | s.Seek(offset, whence) 21 | } 22 | 23 | func ConstWrong(rs ReadSeeker) { // WARN rs can be Seeker 24 | var whence int = 0 25 | rs.Seek(offset, whence) 26 | } 27 | 28 | func LocalConst(s Seeker) { 29 | const offset2 = 2 30 | var whence int = 0 31 | s.Seek(offset2, whence) 32 | } 33 | 34 | func LocalConstWrong(rs ReadSeeker) { // WARN rs can be Seeker 35 | const offset2 = 2 36 | var whence int = 0 37 | rs.Seek(offset2, whence) 38 | } 39 | 40 | func AssignFromConst() { 41 | var i int 42 | i = offset 43 | println(i) 44 | } 45 | -------------------------------------------------------------------------------- /check/testdata/files/convert.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type mint int 4 | 5 | func (m mint) Close() error { 6 | return nil 7 | } 8 | 9 | type mint2 mint 10 | 11 | func ConvertNamed(m mint) { 12 | m.Close() 13 | _ = mint2(m) 14 | } 15 | 16 | func ConvertBasic(m mint) { 17 | m.Close() 18 | println(int(m)) 19 | } 20 | 21 | type Closer interface { 22 | Close() error 23 | } 24 | 25 | func ConvertIface(m mint) { // WARN m can be Closer 26 | m.Close() 27 | _ = Closer(m) 28 | } 29 | -------------------------------------------------------------------------------- /check/testdata/files/dereference.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Closer interface { 4 | Close() 5 | } 6 | 7 | type st struct{} 8 | 9 | func (s *st) Close() {} 10 | 11 | func Wrong(s st) { // WARN s can be Closer 12 | s.Close() 13 | s = st{} 14 | } 15 | 16 | func Dereferenced(s *st) { 17 | s.Close() 18 | *s = st{} 19 | } 20 | -------------------------------------------------------------------------------- /check/testdata/files/fields.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Closer interface { 4 | Close() error 5 | } 6 | 7 | type st struct { 8 | field int 9 | m map[int]int 10 | } 11 | 12 | func (s *st) Close() error { 13 | return nil 14 | } 15 | 16 | func (s *st) Lock() {} 17 | 18 | func (s *st) Unlock() {} 19 | 20 | func Foo(s *st) { 21 | s.Close() 22 | s.field = 3 23 | } 24 | 25 | func FooWrong(s *st) { // WARN s can be Closer 26 | s.Close() 27 | } 28 | 29 | type st2 struct { 30 | st1 *st 31 | } 32 | 33 | func (s *st2) Close() error { 34 | return nil 35 | } 36 | 37 | func Foo2(s *st2) { 38 | s.Close() 39 | s.st1.field = 3 40 | } 41 | 42 | func Foo2Wrong(s *st2) { // WARN s can be Closer 43 | s.Close() 44 | } 45 | 46 | func FooPassed(s *st) { 47 | s.Close() 48 | s2 := s 49 | s2.field = 2 50 | } 51 | 52 | func FooPassedWrong(s *st) { // WARN s can be Closer 53 | s.Close() 54 | s2 := s 55 | s2.Close() 56 | } 57 | 58 | func FooBuiltinArg(s *st) func(int) { 59 | s.Lock() 60 | s.Unlock() 61 | delete(s.m, 3) 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /check/testdata/files/global_call.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | func someFunc() error { 4 | return nil 5 | } 6 | 7 | var globalErr = someFunc() 8 | -------------------------------------------------------------------------------- /check/testdata/files/implement.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Closer interface { 4 | Close() 5 | } 6 | 7 | type ReadCloser interface { 8 | Closer 9 | Read() 10 | } 11 | 12 | type MyFunc func(rc ReadCloser, err error) bool 13 | 14 | func MyFuncImpl(rc ReadCloser, err error) bool { 15 | rc.Close() 16 | return false 17 | } 18 | 19 | func MyFuncWrong(rc ReadCloser, err error) { // WARN rc can be Closer 20 | rc.Close() 21 | } 22 | -------------------------------------------------------------------------------- /check/testdata/files/import.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | import ( 4 | "io" 5 | "os" 6 | ) 7 | 8 | func Empty() { 9 | } 10 | 11 | func Basic(c io.Closer) { 12 | c.Close() 13 | } 14 | 15 | func BasicWrong(rc io.ReadCloser) { // WARN rc can be io.Closer 16 | rc.Close() 17 | } 18 | 19 | type St struct{} 20 | 21 | func (s *St) Basic(c io.Closer) { 22 | c.Close() 23 | } 24 | 25 | func (s *St) BasicWrong(rc io.ReadCloser) { // WARN rc can be io.Closer 26 | rc.Close() 27 | } 28 | 29 | type Namer interface { 30 | Name() string 31 | } 32 | 33 | func WalkFuncImpl(path string, info os.FileInfo, err error) error { 34 | info.Name() 35 | return nil 36 | } 37 | 38 | func WalkFuncImplWrong(path string, info os.FileInfo, err error) { // WARN info can be Namer 39 | info.Name() 40 | } 41 | 42 | type MyWalkFunc func(path string, info os.FileInfo, err error) error 43 | -------------------------------------------------------------------------------- /check/testdata/files/issue22.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type StringerVar string 4 | 5 | func (myx StringerVar) String() string { 6 | return string(myx) 7 | } 8 | 9 | type Stringer interface { 10 | String() string 11 | } 12 | 13 | type SomeInterface interface { 14 | FunctionA(StringerVar) 15 | FunctionB(Stringer) string 16 | } 17 | 18 | type SomeVar struct{} 19 | 20 | func (i SomeVar) FunctionA(a StringerVar) { 21 | i.FunctionB(a) 22 | } 23 | 24 | func (i SomeVar) FunctionB(a Stringer) string { 25 | return a.String() 26 | } 27 | -------------------------------------------------------------------------------- /check/testdata/files/lit_types.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Closer interface { 4 | Close() 5 | } 6 | 7 | func DoCloseOther(rc interface { // WARN rc can be Closer 8 | Close() 9 | Read() 10 | }) { 11 | rc.Close() 12 | } 13 | -------------------------------------------------------------------------------- /check/testdata/files/nested_func.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Closer interface { 4 | Close() error 5 | } 6 | 7 | type Reader interface { 8 | Read(p []byte) (n int, err error) 9 | } 10 | 11 | type ReadCloser interface { 12 | Reader 13 | Closer 14 | } 15 | 16 | func FooGo(rc ReadCloser) { 17 | rc.Read(nil) 18 | go func() { 19 | rc.Close() 20 | }() 21 | } 22 | 23 | func FooArg(rc ReadCloser) { 24 | rc.Read(nil) 25 | f := func(err error) {} 26 | f(rc.Close()) 27 | } 28 | 29 | func FooGoWrong(rc ReadCloser) { // WARN rc can be Closer 30 | go func() { 31 | rc.Close() 32 | }() 33 | } 34 | 35 | func FooArgWrong(rc ReadCloser) { // WARN rc can be Closer 36 | f := func(err error) {} 37 | f(rc.Close()) 38 | } 39 | 40 | func FooNestedWrongIgnored(rc ReadCloser) { // WARN rc can be Reader 41 | f := func(rc ReadCloser) { 42 | rc.Close() 43 | } 44 | f(nil) 45 | b := make([]byte, 10) 46 | rc.Read(b) 47 | } 48 | -------------------------------------------------------------------------------- /check/testdata/files/noniface_usage.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type mint int 4 | 5 | func (m mint) String() string { 6 | return "" 7 | } 8 | 9 | func Unary(m mint) { 10 | m.String() 11 | _ = -m 12 | } 13 | 14 | func BinaryLeft(m mint) { 15 | m.String() 16 | _ = m + 3 17 | } 18 | 19 | func BinaryRight(m mint) { 20 | m.String() 21 | _ = 3 + m 22 | } 23 | 24 | func IncDec(m mint) { 25 | m.String() 26 | m++ 27 | } 28 | 29 | type marr [3]int 30 | 31 | func (m marr) String() string { 32 | return "" 33 | } 34 | 35 | func Index(m marr) { 36 | m.String() 37 | _ = m[1] 38 | } 39 | 40 | func BinaryNonIface(m mint) { 41 | if m > 3 { 42 | m.String() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /check/testdata/files/noninteresting.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type EmptyIface interface{} 4 | 5 | type UninterestingMethods interface { 6 | Foo() error 7 | bar() int 8 | } 9 | 10 | type InterestingUnexported interface { 11 | Foo(f string) error 12 | bar(f string) int 13 | } 14 | 15 | type St struct{} 16 | 17 | func (s St) Foo(f string) {} 18 | 19 | func (s St) nonExported() {} 20 | 21 | func Bar(s St) { 22 | s.Foo("") 23 | } 24 | 25 | type NonInterestingFunc func() error 26 | -------------------------------------------------------------------------------- /check/testdata/files/own_iface.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type FooCloser interface { 4 | Foo() 5 | Close() error 6 | } 7 | 8 | type Barer interface { 9 | Bar(fc FooCloser) int 10 | } 11 | 12 | type St struct{} 13 | 14 | func (s St) Bar(fc FooCloser) int { 15 | return 2 16 | } 17 | 18 | func Foo(s St) { // WARN s can be Barer 19 | _ = s.Bar(nil) 20 | } 21 | 22 | func Bar(fc FooCloser) int { 23 | fc.Close() 24 | return 3 25 | } 26 | -------------------------------------------------------------------------------- /check/testdata/files/param_groups.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Fooer interface { 4 | Foo() 5 | } 6 | 7 | type FooBarer interface { 8 | Fooer 9 | Bar() 10 | } 11 | 12 | func Separate(fb1 FooBarer, fb2 FooBarer) { // WARN fb1 can be Fooer 13 | fb1.Foo() 14 | fb2.Foo() 15 | fb2.Bar() 16 | } 17 | 18 | func Grouped(fb1, fb2 FooBarer) { 19 | fb1.Foo() 20 | fb2.Foo() 21 | fb2.Bar() 22 | } 23 | -------------------------------------------------------------------------------- /check/testdata/files/params.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Closer interface { 4 | Close() error 5 | } 6 | 7 | type Reader interface { 8 | Read(p []byte) (n int, err error) 9 | } 10 | 11 | type ReadCloser interface { 12 | Reader 13 | Closer 14 | } 15 | 16 | type Seeker interface { 17 | Seek(int64, int) (int64, error) 18 | } 19 | 20 | type ReadSeeker interface { 21 | Reader 22 | Seeker 23 | } 24 | 25 | func Args(rc ReadCloser) { 26 | b := make([]byte, 10) 27 | rc.Read(b) 28 | rc.Close() 29 | } 30 | 31 | func ArgsWrong(rc ReadCloser) { // WARN rc can be Reader 32 | b := make([]byte, 10) 33 | rc.Read(b) 34 | } 35 | 36 | func ArgsLit(rs ReadSeeker) { 37 | b := make([]byte, 10) 38 | rs.Read(b) 39 | rs.Seek(20, 0) 40 | } 41 | 42 | func ArgsLitWrong(rs ReadSeeker) { // WARN rs can be Seeker 43 | rs.Seek(20, 0) 44 | } 45 | 46 | func ArgsLit2(rs ReadSeeker) { 47 | rs.Read([]byte{}) 48 | rs.Seek(20, 0) 49 | } 50 | 51 | func ArgsLit2Wrong(rs ReadSeeker) { // WARN rs can be Reader 52 | rs.Read([]byte{}) 53 | } 54 | 55 | func ArgsNil(rs ReadSeeker) { 56 | rs.Read(nil) 57 | rs.Seek(20, 0) 58 | } 59 | 60 | func ArgsNilWrong(rs ReadSeeker) { // WARN rs can be Reader 61 | rs.Read(nil) 62 | } 63 | 64 | type St struct{} 65 | 66 | func (s St) Args(rc ReadCloser) { 67 | var b []byte 68 | rc.Read(b) 69 | rc.Close() 70 | } 71 | 72 | func (s St) ArgsWrong(rc ReadCloser) { // WARN rc can be Reader 73 | b := make([]byte, 10) 74 | rc.Read(b) 75 | } 76 | 77 | type argBad struct{} 78 | 79 | func (a argBad) Read(n int) (int, error) { 80 | return 0, nil 81 | } 82 | 83 | func (a argBad) Close(n int) error { 84 | return nil 85 | } 86 | 87 | type argGood struct{} 88 | 89 | func (a argGood) Read(p []byte) (int, error) { 90 | return 0, nil 91 | } 92 | 93 | func ArgsMismatch(a argBad) { 94 | a.Read(10) 95 | } 96 | 97 | func ArgsMatch(a argGood) { // WARN a can be Reader 98 | b := make([]byte, 10) 99 | a.Read(b) 100 | } 101 | 102 | func ArgsMismatchNum(a argBad) { 103 | a.Close(3) 104 | } 105 | 106 | func ArgsExtra() { 107 | println(12, "foo") 108 | } 109 | 110 | func BuiltinExtra(s string) { 111 | i := 2 112 | b := make([]byte, i) 113 | _ = append(b, s...) 114 | } 115 | -------------------------------------------------------------------------------- /check/testdata/files/readme.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | ) 7 | 8 | func ProcessInput(f *os.File) error { // WARN f can be io.Reader 9 | b, err := ioutil.ReadAll(f) 10 | if err != nil { 11 | return err 12 | } 13 | return processBytes(b) 14 | } 15 | 16 | func processBytes(b []byte) error { return nil } 17 | -------------------------------------------------------------------------------- /check/testdata/files/recursive.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Closer interface { 4 | Close() 5 | } 6 | 7 | type ReadCloser interface { 8 | Closer 9 | Read() 10 | } 11 | 12 | func Recursive(rc ReadCloser) { 13 | rc.Read() 14 | rc.Close() 15 | Recursive(rc) 16 | } 17 | 18 | func RecursiveWrong(rc ReadCloser) { // WARN rc can be Closer 19 | rc.Close() 20 | RecursiveWrong(rc) 21 | } 22 | -------------------------------------------------------------------------------- /check/testdata/files/repeated_types.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Namer interface { 4 | Name() string 5 | } 6 | 7 | type st struct{} 8 | 9 | func (s st) Name() string { 10 | return "" 11 | } 12 | 13 | type MyPathFunc func(path string, s st) error 14 | type MyPathFunc2 func(path string, s st) error 15 | 16 | func Impl(path string, s st) error { 17 | s.Name() 18 | return nil 19 | } 20 | 21 | type MyIface interface { 22 | FooBar(s *st) 23 | } 24 | type MyIface2 interface { 25 | MyIface 26 | } 27 | 28 | type impl struct{} 29 | 30 | func (im impl) FooBar(s *st) {} 31 | 32 | func FooWrong(im impl) { // WARN im can be MyIface 33 | im.FooBar(nil) 34 | } 35 | 36 | type FooBarFunc func(s st) 37 | -------------------------------------------------------------------------------- /check/testdata/files/results.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Closer interface { 4 | Close() error 5 | } 6 | 7 | type ReadCloser interface { 8 | Closer 9 | Read() (int, error) 10 | } 11 | 12 | func Results(rc ReadCloser) { 13 | _, _ = rc.Read() 14 | err := rc.Close() 15 | println(err) 16 | } 17 | 18 | func ResultsWrong(rc ReadCloser) { // WARN rc can be Closer 19 | err := rc.Close() 20 | println(err) 21 | } 22 | 23 | type argBad struct{} 24 | 25 | func (a argBad) Read() (string, error) { 26 | return "", nil 27 | } 28 | 29 | func (a argBad) Write() error { 30 | return nil 31 | } 32 | 33 | func (a argBad) Close() int { 34 | return 0 35 | } 36 | 37 | func ResultsMismatchNumber(a argBad) { 38 | _ = a.Write() 39 | } 40 | 41 | func ResultsMismatchType(a argBad) { 42 | s, _ := a.Read() 43 | println(s) 44 | } 45 | 46 | func ResultsMismatchTypes(a, b argBad) { 47 | r1, r2 := a.Close(), b.Close() 48 | println(r1, r2) 49 | } 50 | -------------------------------------------------------------------------------- /check/testdata/files/shadow.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Closer interface { 4 | Close() error 5 | } 6 | 7 | type FooCloser interface { 8 | Foo() 9 | Close() error 10 | } 11 | 12 | func ShadowArg(fc FooCloser) { // WARN fc can be Closer 13 | fc.Close() 14 | for { 15 | fc := 3 16 | println(fc + 1) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /check/testdata/files/simple.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | func Empty() { 4 | } 5 | 6 | type Closer interface { 7 | Close() 8 | } 9 | 10 | type ReadCloser interface { 11 | Closer 12 | Read() 13 | } 14 | 15 | func Basic(c Closer) { 16 | c.Close() 17 | } 18 | 19 | func BasicInteresting(rc ReadCloser) { 20 | rc.Read() 21 | rc.Close() 22 | } 23 | 24 | func BasicWrong(rc ReadCloser) { // WARN rc can be Closer 25 | rc.Close() 26 | } 27 | 28 | type St struct{} 29 | 30 | func (s *St) Basic(c Closer) { 31 | c.Close() 32 | } 33 | 34 | func (s *St) BasicWrong(rc ReadCloser) { // WARN rc can be Closer 35 | rc.Close() 36 | } 37 | -------------------------------------------------------------------------------- /check/testdata/files/skip_call.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type st struct { 4 | err error 5 | } 6 | 7 | func Foo(s st) { 8 | println(s.err.Error()) 9 | } 10 | 11 | func NoArgs(s int) { 12 | println() 13 | } 14 | 15 | type mint int 16 | 17 | func (m mint) Close() error { 18 | return nil 19 | } 20 | 21 | func ConvertBasic(m mint) { 22 | m.Close() 23 | _ = int64(m) 24 | } 25 | 26 | type mstr string 27 | 28 | func (m mstr) Close() error { 29 | return nil 30 | } 31 | 32 | func ConvertSlice(m mstr) { 33 | m.Close() 34 | _ = []byte(m) 35 | } 36 | 37 | type myFunc func() error 38 | 39 | func ConvertNoArg(f myFunc) { 40 | _ = f() 41 | } 42 | 43 | type Closer interface { 44 | Close() error 45 | } 46 | 47 | func WrongConvertCloser(m mstr) { // WARN m can be Closer 48 | _ = Closer(m) 49 | m.Close() 50 | } 51 | 52 | func WrongFuncLit(m mstr, dc1 func(c Closer)) { // WARN m can be Closer 53 | dc1(m) 54 | } 55 | 56 | type doClose func(c Closer) 57 | 58 | func WrongFuncVar(m mstr, dc2 doClose) { // WARN m can be Closer 59 | dc2(m) 60 | } 61 | -------------------------------------------------------------------------------- /check/testdata/files/unexported.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Closer interface { 4 | Close() 5 | } 6 | 7 | type ReadCloser interface { 8 | Closer 9 | Read() 10 | } 11 | 12 | type myFunc func(rc ReadCloser, err error) int 13 | 14 | func MyFuncImpl(rc ReadCloser, err error) int { 15 | rc.Close() 16 | return 0 17 | } 18 | 19 | func MyFuncWrong(rc ReadCloser, err error) { // WARN rc can be Closer 20 | rc.Close() 21 | } 22 | 23 | type myIface interface { 24 | Foo(rc ReadCloser, i int64) 25 | } 26 | 27 | func FooImpl(rc ReadCloser, i int64) { 28 | rc.Close() 29 | } 30 | 31 | type St struct{} 32 | 33 | func (s *St) Foo(rc ReadCloser, i int64) {} 34 | 35 | func DoNotSuggestUnexportedIface(s *St, rc ReadCloser) { 36 | a := int64(3) 37 | s.Foo(rc, a) 38 | } 39 | -------------------------------------------------------------------------------- /check/testdata/files/unexported_byvalue.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Stringer interface { 4 | String() string 5 | } 6 | 7 | type st struct{} 8 | 9 | func (s st) String() string { 10 | return "foo" 11 | } 12 | 13 | func Exported(s st) string { // WARN s can be Stringer 14 | return s.String() 15 | } 16 | 17 | func unexported(s st) string { 18 | return s.String() 19 | } 20 | -------------------------------------------------------------------------------- /check/testdata/files/unusual_types.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Closer interface { 4 | Close() 5 | } 6 | 7 | type ReadCloser interface { 8 | Closer 9 | Read() 10 | } 11 | 12 | func Basic(s string) { 13 | _ = s 14 | } 15 | 16 | func BasicWrong(rc ReadCloser) { // WARN rc can be Closer 17 | rc.Close() 18 | } 19 | 20 | func Array(ints [3]int) {} 21 | 22 | func ArrayIface(rcs [3]ReadCloser) { 23 | rcs[1].Close() 24 | } 25 | 26 | func Slice(ints []int) {} 27 | 28 | func SliceIface(rcs []ReadCloser) { 29 | rcs[1].Close() 30 | } 31 | 32 | func TypeConversion(i int) int64 { 33 | return int64(i) 34 | } 35 | 36 | func LocalType() { 37 | type str string 38 | } 39 | -------------------------------------------------------------------------------- /check/testdata/files/used.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Closer interface { 4 | Close() error 5 | } 6 | 7 | type Reader interface { 8 | Read(p []byte) (n int, err error) 9 | } 10 | 11 | type ReadCloser interface { 12 | Reader 13 | Closer 14 | } 15 | 16 | type St struct{} 17 | 18 | func (s St) Read(p []byte) (int, error) { 19 | return 0, nil 20 | } 21 | func (s St) Close() error { 22 | return nil 23 | } 24 | func (s St) Other() {} 25 | 26 | func FooCloser(c Closer) { 27 | c.Close() 28 | } 29 | 30 | func FooSt(s St) { 31 | s.Other() 32 | } 33 | 34 | func Bar(s St) { 35 | s.Close() 36 | FooSt(s) 37 | } 38 | 39 | func BarWrong(s St) { // WARN s can be Closer 40 | s.Close() 41 | FooCloser(s) 42 | } 43 | 44 | func extra(n int, cs ...Closer) {} 45 | 46 | func ArgExtraWrong(s1 St) { // WARN s1 can be Closer 47 | var s2 St 48 | s1.Close() 49 | s2.Close() 50 | extra(3, s1, s2) 51 | } 52 | 53 | func Assigned(s St) { 54 | s.Close() 55 | var s2 St 56 | s2 = s 57 | _ = s2 58 | } 59 | 60 | func Declared(s St) { 61 | s.Close() 62 | var s2 St = s 63 | _ = s2 64 | } 65 | 66 | func AssignedIface(s St) { // WARN s can be Closer 67 | s.Close() 68 | var c Closer 69 | c = s 70 | _ = c 71 | } 72 | 73 | func AssignedIfaceDiff(s St) { // WARN s can be ReadCloser 74 | s.Close() 75 | var r Reader 76 | r = s 77 | _ = r 78 | } 79 | 80 | func doRead(r Reader) { 81 | b := make([]byte, 10) 82 | r.Read(b) 83 | } 84 | 85 | func ArgIfaceDiff(s St) { // WARN s can be ReadCloser 86 | s.Close() 87 | doRead(s) 88 | } 89 | -------------------------------------------------------------------------------- /check/testdata/files/used_func.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Closer interface { 4 | Close() 5 | } 6 | 7 | type ReadCloser interface { 8 | Read(p []byte) (n int, err error) 9 | Closer 10 | } 11 | 12 | func Wrong(rc ReadCloser) { // WARN rc can be Closer 13 | rc.Close() 14 | } 15 | 16 | func receiver(f func(ReadCloser)) { 17 | var rc ReadCloser 18 | f(rc) 19 | } 20 | 21 | func Correct(rc ReadCloser) { 22 | rc.Close() 23 | } 24 | 25 | func CorrectUse() { 26 | receiver(Correct) 27 | } 28 | 29 | var holder func(ReadCloser) 30 | 31 | func Correct2(rc ReadCloser) { 32 | rc.Close() 33 | } 34 | 35 | func CorrectAssign() { 36 | holder = Correct2 37 | } 38 | 39 | func CorrectLit() { 40 | f := func(rc ReadCloser) { 41 | rc.Close() 42 | } 43 | receiver(f) 44 | } 45 | 46 | func CorrectLitAssign() { 47 | f := func(rc ReadCloser) { 48 | rc.Close() 49 | } 50 | var f2 func(ReadCloser) 51 | f2 = f 52 | f2(nil) 53 | } 54 | 55 | func CorrectLitDecl() { 56 | f := func(rc ReadCloser) { 57 | rc.Close() 58 | } 59 | var f2 func(ReadCloser) = f 60 | f2(nil) 61 | } 62 | -------------------------------------------------------------------------------- /check/testdata/files/wanted_type.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Closer interface { 4 | Close() 5 | } 6 | 7 | type ReadCloser interface { 8 | Closer 9 | Read() 10 | } 11 | 12 | type Conn struct{} 13 | 14 | func (c Conn) Close() {} 15 | 16 | func DoClose(c Conn) { // WARN c can be Closer 17 | c.Close() 18 | } 19 | 20 | func DoCloseConn(c Conn) { 21 | c.Close() 22 | } 23 | 24 | func DoCloseConnection(c Conn) { 25 | c.Close() 26 | } 27 | 28 | type bar struct{} 29 | 30 | func (f *bar) Close() {} 31 | 32 | func barClose(b *bar) { 33 | b.Close() 34 | } 35 | 36 | func DoCloseFoobar(b *bar) { // WARN b can be Closer 37 | b.Close() 38 | } 39 | 40 | type NetConn struct{} 41 | 42 | func (n NetConn) Close() {} 43 | 44 | func closeConn(conn *NetConn) { 45 | conn.Close() 46 | } 47 | -------------------------------------------------------------------------------- /check/testdata/local/multipkg/pkg2/simple.go: -------------------------------------------------------------------------------- 1 | package pkg2 2 | 3 | type Closer interface { 4 | Close() 5 | } 6 | 7 | type ReadCloser interface { 8 | Closer 9 | Read() 10 | } 11 | 12 | func BasicWrong(rc ReadCloser) { // WARN rc can be Closer 13 | rc.Close() 14 | } 15 | -------------------------------------------------------------------------------- /check/testdata/local/multipkg/simple.go: -------------------------------------------------------------------------------- 1 | package multipkg 2 | -------------------------------------------------------------------------------- /check/testdata/local/single/simple.go: -------------------------------------------------------------------------------- 1 | package single 2 | 3 | func Empty() { 4 | } 5 | 6 | type Closer interface { 7 | Close() 8 | } 9 | 10 | type ReadCloser interface { 11 | Closer 12 | Read() 13 | } 14 | 15 | func Basic(c Closer) { 16 | c.Close() 17 | } 18 | 19 | func BasicWrong(rc ReadCloser) { // WARN rc can be Closer 20 | rc.Close() 21 | } 22 | 23 | func OtherWrong(s St) { // WARN s can be Closer 24 | s.Close() 25 | } 26 | -------------------------------------------------------------------------------- /check/testdata/local/single/struct.go: -------------------------------------------------------------------------------- 1 | package single 2 | 3 | type St struct{} 4 | 5 | func (s *St) Close() {} 6 | 7 | func (s *St) Basic(c Closer) { 8 | c.Close() 9 | } 10 | 11 | func (s *St) BasicWrong(rc ReadCloser) { // WARN rc can be Closer 12 | rc.Close() 13 | } 14 | -------------------------------------------------------------------------------- /check/testdata/src/deps/import.go: -------------------------------------------------------------------------------- 1 | // +build go1.6 2 | 3 | package single 4 | 5 | import "foo/bar" 6 | 7 | var _ = bar.Bar 8 | 9 | type Closer interface { 10 | Close() 11 | } 12 | 13 | type ReadCloser interface { 14 | Closer 15 | Read() 16 | } 17 | -------------------------------------------------------------------------------- /check/testdata/src/deps/import15.go: -------------------------------------------------------------------------------- 1 | // +build !go1.6 2 | 3 | package single 4 | 5 | type Closer interface { 6 | Close() 7 | } 8 | 9 | type ReadCloser interface { 10 | Closer 11 | Read() 12 | } 13 | -------------------------------------------------------------------------------- /check/testdata/src/deps/single.go: -------------------------------------------------------------------------------- 1 | package single 2 | 3 | func BasicWrong(rc ReadCloser) { // WARN rc can be Closer 4 | rc.Close() 5 | } 6 | -------------------------------------------------------------------------------- /check/testdata/src/deps/vendor/foo/bar/def.go: -------------------------------------------------------------------------------- 1 | package bar 2 | 3 | var Bar = 0 4 | -------------------------------------------------------------------------------- /check/testdata/src/grab-import/def/def.go: -------------------------------------------------------------------------------- 1 | package def 2 | 3 | type ReadCloser interface { 4 | Read([]byte) (int, error) 5 | Close() error 6 | } 7 | 8 | type FooFunc func(ReadCloser, int) int 9 | 10 | var SomeVar int = 3 11 | 12 | func SomeFunc() {} 13 | -------------------------------------------------------------------------------- /check/testdata/src/grab-import/def/nested/def.go: -------------------------------------------------------------------------------- 1 | package nested 2 | 3 | type Fooer interface { 4 | Foo() 5 | } 6 | -------------------------------------------------------------------------------- /check/testdata/src/grab-import/use.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | import ( 4 | "grab-import/def" 5 | def2 "grab-import/def/nested" 6 | ) 7 | 8 | type St struct{} 9 | 10 | func (s *St) Foo(rc def.ReadCloser, i int) int { 11 | rc.Close() 12 | return def.SomeVar 13 | } 14 | 15 | func NonInterestingCall() { 16 | def.SomeFunc() 17 | } 18 | 19 | func Foo(f def2.Fooer) { 20 | f.Foo() 21 | } 22 | 23 | type st2 struct{} 24 | 25 | func (s st2) Foo() 26 | 27 | func FooWrong(s st2) { // WARN s can be grab-import/def/nested.Fooer 28 | s.Foo() 29 | } 30 | -------------------------------------------------------------------------------- /check/testdata/src/nested/pkg/simple.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type Closer interface { 4 | Close() 5 | } 6 | 7 | type ReadCloser interface { 8 | Closer 9 | Read() 10 | } 11 | 12 | func BasicWrong(rc ReadCloser) { // WARN rc can be Closer 13 | rc.Close() 14 | } 15 | -------------------------------------------------------------------------------- /check/testdata/src/single/simple.go: -------------------------------------------------------------------------------- 1 | package single 2 | 3 | func Empty() { 4 | } 5 | 6 | type Closer interface { 7 | Close() 8 | } 9 | 10 | type ReadCloser interface { 11 | Closer 12 | Read() 13 | } 14 | 15 | func Basic(c Closer) { 16 | c.Close() 17 | } 18 | 19 | func BasicWrong(rc ReadCloser) { // WARN rc can be Closer 20 | rc.Close() 21 | } 22 | 23 | func OtherWrong(s St) { // WARN s can be Closer 24 | s.Close() 25 | } 26 | -------------------------------------------------------------------------------- /check/testdata/src/single/struct.go: -------------------------------------------------------------------------------- 1 | package single 2 | 3 | type St struct{} 4 | 5 | func (s *St) Close() {} 6 | 7 | func (s *St) Basic(c Closer) { 8 | c.Close() 9 | } 10 | 11 | func (s *St) BasicWrong(rc ReadCloser) { // WARN rc can be Closer 12 | rc.Close() 13 | } 14 | -------------------------------------------------------------------------------- /check/testdata/src/skip/.hidden/simple.go: -------------------------------------------------------------------------------- 1 | package simple 2 | -------------------------------------------------------------------------------- /check/testdata/src/skip/simple.go: -------------------------------------------------------------------------------- 1 | package skip 2 | 3 | func Empty() { 4 | } 5 | 6 | type Closer interface { 7 | Close() 8 | } 9 | 10 | type ReadCloser interface { 11 | Closer 12 | Read() 13 | } 14 | 15 | func Basic(c Closer) { 16 | c.Close() 17 | } 18 | 19 | func BasicWrong(rc ReadCloser) { // WARN rc can be Closer 20 | rc.Close() 21 | } 22 | -------------------------------------------------------------------------------- /check/testdata/src/skip/testdata/simple.go: -------------------------------------------------------------------------------- 1 | package foo 2 | -------------------------------------------------------------------------------- /check/testdata/src/skip/vendor/simple.go: -------------------------------------------------------------------------------- 1 | package foo 2 | -------------------------------------------------------------------------------- /check/types.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Daniel Martí 2 | // See LICENSE for licensing information 3 | 4 | package check 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "go/types" 10 | "sort" 11 | "strings" 12 | ) 13 | 14 | type methoder interface { 15 | NumMethods() int 16 | Method(int) *types.Func 17 | } 18 | 19 | func methoderFuncMap(m methoder, skip bool) map[string]string { 20 | ifuncs := make(map[string]string, m.NumMethods()) 21 | for i := 0; i < m.NumMethods(); i++ { 22 | f := m.Method(i) 23 | if !f.Exported() { 24 | if skip { 25 | continue 26 | } 27 | return nil 28 | } 29 | sign := f.Type().(*types.Signature) 30 | ifuncs[f.Name()] = signString(sign) 31 | } 32 | return ifuncs 33 | } 34 | 35 | func typeFuncMap(t types.Type) map[string]string { 36 | switch x := t.(type) { 37 | case *types.Pointer: 38 | return typeFuncMap(x.Elem()) 39 | case *types.Named: 40 | u := x.Underlying() 41 | if types.IsInterface(u) { 42 | return typeFuncMap(u) 43 | } 44 | return methoderFuncMap(x, true) 45 | case *types.Interface: 46 | return methoderFuncMap(x, false) 47 | default: 48 | return nil 49 | } 50 | } 51 | 52 | func funcMapString(iface map[string]string) string { 53 | fnames := make([]string, 0, len(iface)) 54 | for fname := range iface { 55 | fnames = append(fnames, fname) 56 | } 57 | sort.Strings(fnames) 58 | var b bytes.Buffer 59 | for i, fname := range fnames { 60 | if i > 0 { 61 | fmt.Fprint(&b, "; ") 62 | } 63 | fmt.Fprint(&b, fname, iface[fname]) 64 | } 65 | return b.String() 66 | } 67 | 68 | func tupleJoin(buf *bytes.Buffer, t *types.Tuple) { 69 | buf.WriteByte('(') 70 | for i := 0; i < t.Len(); i++ { 71 | if i > 0 { 72 | buf.WriteString(", ") 73 | } 74 | buf.WriteString(t.At(i).Type().String()) 75 | } 76 | buf.WriteByte(')') 77 | } 78 | 79 | // signString is similar to Signature.String(), but it ignores 80 | // param/result names. 81 | func signString(sign *types.Signature) string { 82 | var buf bytes.Buffer 83 | tupleJoin(&buf, sign.Params()) 84 | tupleJoin(&buf, sign.Results()) 85 | return buf.String() 86 | } 87 | 88 | func interesting(t types.Type) bool { 89 | switch x := t.(type) { 90 | case *types.Interface: 91 | return x.NumMethods() > 1 92 | case *types.Named: 93 | if u := x.Underlying(); types.IsInterface(u) { 94 | return interesting(u) 95 | } 96 | return x.NumMethods() >= 1 97 | case *types.Pointer: 98 | return interesting(x.Elem()) 99 | default: 100 | return false 101 | } 102 | } 103 | 104 | func anyInteresting(params *types.Tuple) bool { 105 | for i := 0; i < params.Len(); i++ { 106 | t := params.At(i).Type() 107 | if interesting(t) { 108 | return true 109 | } 110 | } 111 | return false 112 | } 113 | 114 | func fromScope(scope *types.Scope) (ifaces map[string]string, funcs map[string]bool) { 115 | ifaces = make(map[string]string) 116 | funcs = make(map[string]bool) 117 | for _, name := range scope.Names() { 118 | tn, ok := scope.Lookup(name).(*types.TypeName) 119 | if !ok { 120 | continue 121 | } 122 | switch x := tn.Type().Underlying().(type) { 123 | case *types.Interface: 124 | iface := methoderFuncMap(x, false) 125 | if len(iface) == 0 { 126 | continue 127 | } 128 | for i := 0; i < x.NumMethods(); i++ { 129 | f := x.Method(i) 130 | sign := f.Type().(*types.Signature) 131 | if !anyInteresting(sign.Params()) { 132 | continue 133 | } 134 | funcs[signString(sign)] = true 135 | } 136 | s := funcMapString(iface) 137 | if _, e := ifaces[s]; !e { 138 | ifaces[s] = tn.Name() 139 | } 140 | case *types.Signature: 141 | if !anyInteresting(x.Params()) { 142 | continue 143 | } 144 | funcs[signString(x)] = true 145 | } 146 | } 147 | return ifaces, funcs 148 | } 149 | 150 | func mentionsName(fname, name string) bool { 151 | if len(name) < 2 { 152 | return false 153 | } 154 | capit := strings.ToUpper(name[:1]) + name[1:] 155 | lower := strings.ToLower(name) 156 | return strings.Contains(fname, capit) || strings.HasPrefix(fname, lower) 157 | } 158 | 159 | func typeNamed(t types.Type) *types.Named { 160 | for { 161 | switch x := t.(type) { 162 | case *types.Named: 163 | return x 164 | case *types.Pointer: 165 | t = x.Elem() 166 | default: 167 | return nil 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Daniel Martí 2 | // See LICENSE for licensing information 3 | 4 | package main // import "mvdan.cc/interfacer" 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "go/build" 10 | "os" 11 | 12 | "golang.org/x/tools/go/buildutil" 13 | 14 | "mvdan.cc/interfacer/check" 15 | ) 16 | 17 | func init() { 18 | flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", 19 | buildutil.TagsFlagDoc) 20 | } 21 | 22 | func main() { 23 | flag.Parse() 24 | lines, err := check.CheckArgs(flag.Args()) 25 | if err != nil { 26 | fmt.Fprintln(os.Stderr, err) 27 | os.Exit(1) 28 | } 29 | for _, line := range lines { 30 | fmt.Println(line) 31 | } 32 | } 33 | --------------------------------------------------------------------------------