├── Gopkg.toml ├── .drone.sec.sample.yaml ├── .travis.yml ├── cmd └── gosumcheck │ └── main.go ├── .drone.sec ├── Gopkg.lock ├── LICENSE ├── .drone.yml ├── vendor └── honnef.co │ └── go │ └── lint │ ├── lint_test.go │ ├── LICENSE │ ├── lintutil │ └── util.go │ └── lint.go ├── README.md ├── gosum.go ├── checker └── checker.go └── gosum_test.go /Gopkg.toml: -------------------------------------------------------------------------------- 1 | 2 | [[dependencies]] 3 | name = "honnef.co/go/lint" 4 | revision = "f6a1962ee8ee977020724ccffdf5cd135956aa54" 5 | -------------------------------------------------------------------------------- /.drone.sec.sample.yaml: -------------------------------------------------------------------------------- 1 | # copy this file to .drone.sec.yaml and insert 2 | # cp .drone.sec.sample.yaml .drone.sec.yaml 3 | environment: 4 | REVIEWDOG_GITHUB_API_TOKEN: 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.7 5 | - tip 6 | 7 | install: 8 | - go get -d -v -t ./... 9 | - go get github.com/mattn/goveralls 10 | 11 | script: 12 | - go test -v -race ./... 13 | - goveralls -service=travis-ci 14 | -------------------------------------------------------------------------------- /cmd/gosumcheck/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/haya14busa/gosum/checker" 7 | "honnef.co/go/lint/lintutil" 8 | ) 9 | 10 | func main() { 11 | lintutil.ProcessArgs("gosumcheck", checker.NewChecker(), os.Args[1:]) 12 | } 13 | -------------------------------------------------------------------------------- /.drone.sec: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.MKbgQJ2k00t5y7-_lCiDgEodNcLWwhHduuKqpff5lvHPdTvIIv_Ary7WlE9WoXZzvXM4jtLddUqt4fmJzIEikMCfyxCRbQPfhGlwt88tzYiFAmu8ellLUs79phpCTdXd3UrJ8mD7jbdcws1CrTDyDvFNuae1DBmB_ai3DWRQRcAHwm_K4FyqDXPS8-XJ6ncQuHqcyR20SMmTEtyoWFORZZTp4xmGbaeDf67DveR6WYImCi_8hHqkyY15O_2z85Hc59QAIyQPoNhQcahWG6xPlMull5YnvQzCM0yp7liX0E8Kom0hyhK-GSzBO6Liallsw1s5mKDew5FQfVTarVaVpA.HG3jlgusNSAdY02K.Yj6P1MkXOqam9wcOA8ziXSNM-HRJ4B9i-D6_w_sgAApflXMMJP45Q_2GrgWNnj9FbkOKATyHS_UqeQwT46Rlu9I0MtUL0w46vmZXYd4mPYsGf-iXZMGk3eR8-3_LTeEYm5jsMLf65ahtCqkeVHjLwmfNaspckvfMS4MiCCQwdIyBBR-qDsK3JyQY_8N4E-ylIgAci0CWRTBswHayL9Ga.Lt3uQh6ZLZD02ophceaB8g -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | memo = "3a61846314647f5830680efd63489873dd8865586357771e669a024a61b55fd7" 2 | 3 | [[projects]] 4 | branch = "master" 5 | name = "github.com/kisielk/gotool" 6 | packages = ["."] 7 | revision = "0de1eaf82fa3f583ce21fde859f1e7e0c5e9b220" 8 | 9 | [[projects]] 10 | branch = "master" 11 | name = "golang.org/x/tools" 12 | packages = ["go/ast/astutil","go/buildutil","go/loader","go/ssa","go/types/typeutil"] 13 | revision = "15b145e4e3bee8ae65bfd262016b9fa6e144f40b" 14 | 15 | [[projects]] 16 | name = "honnef.co/go/lint" 17 | packages = [".","lintutil"] 18 | revision = "f6a1962ee8ee977020724ccffdf5cd135956aa54" 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 haya14busa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.drone.yml: -------------------------------------------------------------------------------- 1 | # Run the below command when you edit .drone.yml 2 | # drone secure --repo haya14busa/gosum --in .drone.sec.yaml 3 | # 4 | build: 5 | test: 6 | image: golang 7 | commands: 8 | - go get -d -v -t ./... 9 | - go test -v ./... 10 | lint: 11 | image: golang 12 | environment: 13 | - REVIEWDOG_GITHUB_API_TOKEN=$$REVIEWDOG_GITHUB_API_TOKEN 14 | commands: 15 | - go get github.com/haya14busa/reviewdog/cmd/reviewdog 16 | - go get github.com/golang/lint/golint 17 | - go get honnef.co/go/unused/cmd/unused 18 | - go get github.com/kisielk/errcheck 19 | - go get honnef.co/go/staticcheck/cmd/staticcheck 20 | - go get honnef.co/go/simple/cmd/gosimple 21 | - | 22 | go tool vet -all -shadowstrict . 2>&1 | reviewdog -f=govet -ci=droneio 23 | - | 24 | golint ./... | reviewdog -f=golint -ci=droneio 25 | - | 26 | unused ./... | reviewdog -efm="%f:%l:%c: %m" -name=unused -ci=droneio 27 | - | 28 | errcheck -asserts -ignoretests -blank ./... | reviewdog -efm="%f:%l:%c:%m" -name=errcheck -ci=droneio 29 | - | 30 | staticcheck ./... | reviewdog -efm="%f:%l:%c: %m" -name=staticcheck -ci=droneio 31 | - | 32 | gosimple ./... | reviewdog -efm="%f:%l:%c: %m" -name=gosimple -ci=droneio 33 | when: 34 | event: pull_request 35 | -------------------------------------------------------------------------------- /vendor/honnef.co/go/lint/lint_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 The Go Authors. All rights reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd. 6 | 7 | package lint 8 | 9 | import ( 10 | "go/ast" 11 | "go/parser" 12 | "go/token" 13 | "go/types" 14 | "testing" 15 | ) 16 | 17 | func TestExportedType(t *testing.T) { 18 | tests := []struct { 19 | typString string 20 | exp bool 21 | }{ 22 | {"int", true}, 23 | {"string", false}, // references the shadowed builtin "string" 24 | {"T", true}, 25 | {"t", false}, 26 | {"*T", true}, 27 | {"*t", false}, 28 | {"map[int]complex128", true}, 29 | } 30 | for _, test := range tests { 31 | src := `package foo; type T int; type t int; type string struct{}` 32 | fset := token.NewFileSet() 33 | file, err := parser.ParseFile(fset, "foo.go", src, 0) 34 | if err != nil { 35 | t.Fatalf("Parsing %q: %v", src, err) 36 | } 37 | // use the package name as package path 38 | config := &types.Config{} 39 | pkg, err := config.Check(file.Name.Name, fset, []*ast.File{file}, nil) 40 | if err != nil { 41 | t.Fatalf("Type checking %q: %v", src, err) 42 | } 43 | tv, err := types.Eval(fset, pkg, token.NoPos, test.typString) 44 | if err != nil { 45 | t.Errorf("types.Eval(%q): %v", test.typString, err) 46 | continue 47 | } 48 | if got := ExportedType(tv.Type); got != test.exp { 49 | t.Errorf("exportedType(%v) = %t, want %t", tv.Type, got, test.exp) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /vendor/honnef.co/go/lint/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 The Go Authors. All rights reserved. 2 | Copyright (c) 2016 Dominik Honnef. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Google Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Sum/Union/Variant Type in Go and Static Check Tool of switch-case handling 2 | 3 | [![GoDoc](https://godoc.org/github.com/haya14busa/gosum?status.svg)](https://godoc.org/github.com/haya14busa/gosum) 4 | [![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 5 | 6 | ## gosumcheck 7 | 8 | ### Installation 9 | 10 | ``` 11 | $ go get -u github.com/haya14busa/gosum/cmd/gosumcheck 12 | ``` 13 | 14 | ### Example 15 | 16 | ``` 17 | $ gosumcheck go/ast/... 18 | /usr/lib/go/src/go/ast/commentmap.go:233:3: uncovered cases for ast.Node type switch: 19 | - *ast.ChanType 20 | - *ast.Ident 21 | - *ast.SelectorExpr 22 | - *ast.TypeAssertExpr 23 | - *ast.CompositeLit 24 | - *ast.FieldList 25 | - *ast.Package 26 | - *ast.ArrayType 27 | - *ast.ParenExpr 28 | - *ast.BinaryExpr 29 | - *ast.UnaryExpr 30 | - *ast.BadExpr 31 | - *ast.FuncLit 32 | - *ast.CommentGroup 33 | - *ast.IndexExpr 34 | - *ast.MapType 35 | - *ast.StructType 36 | - *ast.BasicLit 37 | - *ast.Ellipsis 38 | - *ast.InterfaceType 39 | - *ast.Comment 40 | - *ast.FuncType 41 | - *ast.SliceExpr 42 | - *ast.StarExpr 43 | - *ast.CallExpr 44 | - *ast.KeyValueExpr 45 | /usr/lib/go/src/go/ast/filter.go:158:2: uncovered cases for ast.Spec type switch: 46 | - *ast.ImportSpec 47 | /usr/lib/go/src/go/ast/filter.go:209:2: uncovered cases for ast.Decl type switch: 48 | - *ast.BadDecl 49 | ``` 50 | 51 | See `gosumcheck -h` and [Sum/Union/Variant Type in Go and Static Check Tool of switch-case handling](https://medium.com/@haya14busa/sum-union-variant-type-in-go-and-static-check-tool-of-switch-case-handling-3bfc61618b1e) for detail. 52 | -------------------------------------------------------------------------------- /gosum.go: -------------------------------------------------------------------------------- 1 | // Package gosum provides utilities for Sum/Union/Variant like types. 2 | package gosum 3 | 4 | import "go/types" 5 | 6 | // SumInterface represents Sum type by interface{} type. 7 | type SumInterface struct { 8 | // underling interface type. It uses types.Named instead of types.Interface 9 | // for convenience. 10 | NamedInterface *types.Named 11 | 12 | // is "internal interface" which includes unexported methods, otherwise 13 | // "public" interface. 14 | IsInternal bool 15 | 16 | // Implements holds types which implement SumInterface type. 17 | Implements struct { 18 | Interfaces []*SumInterface 19 | Pointers []*types.Pointer 20 | } 21 | 22 | // "Implements" only holds types in PkgScope. It's especially for "public" 23 | // interface. 24 | PkgScope []*types.Package 25 | } 26 | 27 | // NewSumInterface returns SumInterface from given interface type. If the given 28 | // interface is "public", this function searches implemented types in pkgscope. 29 | // pkgscope can be nil and it's useful to pass nil if you know the interface is 30 | // "internal" interface. 31 | func NewSumInterface(namedInterface *types.Named, pkgscope []*types.Package) *SumInterface { 32 | seen := make(map[*types.Named]bool) 33 | return newSumInterface(seen, namedInterface, pkgscope) 34 | } 35 | 36 | func newSumInterface(seen map[*types.Named]bool, namedInterface *types.Named, pkgscope []*types.Package) *SumInterface { 37 | if _, ok := seen[namedInterface]; ok { 38 | return nil 39 | } 40 | seen[namedInterface] = true 41 | 42 | i, ok := namedInterface.Underlying().(*types.Interface) 43 | if !ok || i.Empty() { 44 | return nil 45 | } 46 | 47 | sum := &SumInterface{ 48 | NamedInterface: namedInterface, 49 | IsInternal: IsInternalInterface(i), 50 | } 51 | 52 | definedpkg := namedInterface.Obj().Pkg() 53 | pkgs := pkgscope 54 | if definedpkg != nil { 55 | if sum.IsInternal { 56 | // package scope includes only defined package for "internal" interface. 57 | pkgs = []*types.Package{definedpkg} 58 | } else { 59 | // add defined package to package scope if the scope doesn't have the 60 | // defiend package. 61 | found := false 62 | for _, p := range pkgs { 63 | found = (p == definedpkg) 64 | if found { 65 | break 66 | } 67 | } 68 | if !found { 69 | pkgs = append(pkgs, definedpkg) 70 | } 71 | } 72 | } 73 | sum.PkgScope = pkgs 74 | 75 | // Test assignability of all distinct pairs of 76 | // named types (T, U) where U is an interface. 77 | U := namedInterface 78 | for _, pkg := range pkgs { 79 | for _, name := range pkg.Scope().Names() { 80 | if obj, ok := pkg.Scope().Lookup(name).(*types.TypeName); ok { 81 | T := obj.Type() 82 | if T == U { 83 | continue 84 | } 85 | if types.AssignableTo(T, U) { // as interface 86 | if i := newSumInterface(seen, T.(*types.Named), pkgscope); i != nil { 87 | sum.Implements.Interfaces = append(sum.Implements.Interfaces, i) 88 | } 89 | } else if !types.IsInterface(T) { // as pointer 90 | if ptr := types.NewPointer(T); types.AssignableTo(ptr, U) { 91 | sum.Implements.Pointers = append(sum.Implements.Pointers, ptr) 92 | } 93 | } 94 | } 95 | } 96 | } 97 | 98 | return sum 99 | } 100 | 101 | // IsInternalInterface returns true if the given interface has unexported 102 | // method. 103 | func IsInternalInterface(iface *types.Interface) bool { 104 | for i := 0; i < iface.NumMethods(); i++ { 105 | if !iface.Method(i).Exported() { 106 | return true 107 | } 108 | } 109 | return false 110 | } 111 | -------------------------------------------------------------------------------- /checker/checker.go: -------------------------------------------------------------------------------- 1 | package checker 2 | 3 | import ( 4 | "go/ast" 5 | "go/types" 6 | "log" 7 | "strings" 8 | 9 | "github.com/haya14busa/gosum" 10 | "golang.org/x/tools/go/types/typeutil" 11 | "honnef.co/go/lint" 12 | ) 13 | 14 | type Checker struct{} 15 | 16 | func NewChecker() *Checker { 17 | return &Checker{} 18 | } 19 | 20 | func (c *Checker) Init(*lint.Program) {} 21 | 22 | func (c *Checker) Funcs() map[string]lint.Func { 23 | return map[string]lint.Func{ 24 | "SA1000": CheckSwitch, 25 | } 26 | } 27 | 28 | // CheckSwitch checkes possible missed cases for type switch statement. 29 | func CheckSwitch(f *lint.File) { 30 | all := typeutil.Dependencies(f.Pkg.TypesPkg) 31 | info := f.Pkg.TypesInfo 32 | 33 | fn := func(node ast.Node) bool { 34 | typeswitch, ok := node.(*ast.TypeSwitchStmt) 35 | if !ok { 36 | return true 37 | } 38 | named := switchInterfaceType(typeswitch, info) 39 | if named == nil { 40 | return true 41 | } 42 | iface := gosum.NewSumInterface(named, all) 43 | if iface == nil { 44 | return true 45 | } 46 | covered := make(map[types.Type]bool, len(iface.Implements.Pointers)) 47 | for _, ptr := range iface.Implements.Pointers { 48 | covered[ptr.Elem()] = false 49 | } 50 | 51 | coveredTypsByInterface := make(map[*types.Named][]types.Type) 52 | for _, i := range iface.Implements.Interfaces { 53 | name := i.NamedInterface 54 | coveredTypsByInterface[name] = make([]types.Type, len(i.Implements.Pointers)) 55 | for j, ptr := range i.Implements.Pointers { 56 | coveredTypsByInterface[name][j] = ptr.Elem() 57 | } 58 | } 59 | 60 | for _, caseClause := range typeswitch.Body.List { 61 | c, ok := caseClause.(*ast.CaseClause) 62 | if !ok { 63 | log.Printf("got unexpected node: %v", caseClause) 64 | continue 65 | } 66 | if c.List == nil { 67 | // TODO: handle default clause 68 | } else { 69 | for _, expr := range c.List { 70 | tv, ok := info.Types[expr] 71 | if !ok { 72 | // Just ignore this case and continue. 73 | // log.Printf("fail to got type: %v", expr) 74 | // You can see sample cases when you run gosumcheck to docker. 75 | // $ gosumcheck github.com/docker/docker/... 76 | // 2016/12/04 23:57:42 fail to got type: &{client ErrRepoNotInitialized} 77 | // 2016/12/04 23:57:42 fail to got type: &{client ErrRepositoryNotExist} 78 | // 2016/12/04 23:57:42 fail to got type: &{signed ErrExpired} 79 | // ... 80 | continue 81 | } 82 | typ := tv.Type 83 | if types.IsInterface(typ) { 84 | for _, typ := range coveredTypsByInterface[typ.(*types.Named)] { 85 | covered[typ] = true 86 | } 87 | } else { 88 | if ptr, ok := typ.(*types.Pointer); ok { 89 | covered[ptr.Elem()] = true 90 | } // else: untyped nil 91 | } 92 | } 93 | } 94 | } 95 | 96 | var uncovered []types.Type 97 | for elem, b := range covered { 98 | if !b { 99 | uncovered = append(uncovered, elem) 100 | } 101 | } 102 | 103 | if len(uncovered) > 0 { 104 | confidence := 0.6 + 0.4*(1-float64(len(uncovered))/float64(len(covered))) 105 | typs := make([]string, len(uncovered)) 106 | for i, typ := range uncovered { 107 | typs[i] = types.NewPointer(typ).String() 108 | } 109 | if confidence > 0.8 { 110 | f.Errorf(typeswitch, "uncovered cases for %v type switch\n\t- %v", named.String(), strings.Join(typs, "\n\t- ")) 111 | } 112 | } 113 | 114 | return true 115 | } 116 | f.Walk(fn) 117 | } 118 | 119 | // switchInterfaceType returns interface type of type switch statement. It may 120 | // return nil. 121 | func switchInterfaceType(node *ast.TypeSwitchStmt, info types.Info) *types.Named { 122 | ae := assertExpr(node) 123 | if ae == nil { 124 | return nil 125 | } 126 | tv, ok := info.Types[ae.X] 127 | if !ok { 128 | return nil 129 | } 130 | 131 | if named, ok := tv.Type.(*types.Named); ok && types.IsInterface(named) { 132 | return named 133 | } 134 | return nil 135 | } 136 | 137 | func assertExpr(x *ast.TypeSwitchStmt) *ast.TypeAssertExpr { 138 | switch a := x.Assign.(type) { 139 | case *ast.AssignStmt: // x := y.(type) 140 | for _, expr := range a.Rhs { 141 | ae, ok := expr.(*ast.TypeAssertExpr) 142 | if !ok { 143 | continue 144 | } 145 | return ae 146 | } 147 | return nil 148 | case *ast.ExprStmt: // y.(type) 149 | ae, ok := a.X.(*ast.TypeAssertExpr) 150 | if !ok { 151 | return nil 152 | } 153 | return ae 154 | } 155 | return nil 156 | } 157 | -------------------------------------------------------------------------------- /vendor/honnef.co/go/lint/lintutil/util.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 The Go Authors. All rights reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd. 6 | 7 | // Package lintutil provides helpers for writing linter command lines. 8 | package lintutil // import "honnef.co/go/lint/lintutil" 9 | 10 | import ( 11 | "errors" 12 | "flag" 13 | "fmt" 14 | "go/build" 15 | "go/parser" 16 | "log" 17 | "os" 18 | "strings" 19 | 20 | "honnef.co/go/lint" 21 | 22 | "github.com/kisielk/gotool" 23 | "golang.org/x/tools/go/loader" 24 | ) 25 | 26 | func usage(name string, flags *flag.FlagSet) func() { 27 | return func() { 28 | fmt.Fprintf(os.Stderr, "Usage of %s:\n", name) 29 | fmt.Fprintf(os.Stderr, "\t%s [flags] # runs on package in current directory\n", name) 30 | fmt.Fprintf(os.Stderr, "\t%s [flags] packages\n", name) 31 | fmt.Fprintf(os.Stderr, "\t%s [flags] directory\n", name) 32 | fmt.Fprintf(os.Stderr, "\t%s [flags] files... # must be a single package\n", name) 33 | fmt.Fprintf(os.Stderr, "Flags:\n") 34 | flags.PrintDefaults() 35 | } 36 | } 37 | 38 | type runner struct { 39 | checker lint.Checker 40 | tags []string 41 | ignores []lint.Ignore 42 | 43 | unclean bool 44 | } 45 | 46 | func (runner runner) resolveRelative(importPaths []string) (goFiles bool, err error) { 47 | if len(importPaths) == 0 { 48 | return false, nil 49 | } 50 | if strings.HasSuffix(importPaths[0], ".go") { 51 | // User is specifying a package in terms of .go files, don't resolve 52 | return true, nil 53 | } 54 | wd, err := os.Getwd() 55 | if err != nil { 56 | return false, err 57 | } 58 | ctx := build.Default 59 | ctx.BuildTags = runner.tags 60 | for i, path := range importPaths { 61 | bpkg, err := ctx.Import(path, wd, build.FindOnly) 62 | if err != nil { 63 | return false, fmt.Errorf("can't load package %q: %v", path, err) 64 | } 65 | importPaths[i] = bpkg.ImportPath 66 | } 67 | return false, nil 68 | } 69 | 70 | func parseIgnore(s string) ([]lint.Ignore, error) { 71 | var out []lint.Ignore 72 | if len(s) == 0 { 73 | return nil, nil 74 | } 75 | for _, part := range strings.Fields(s) { 76 | p := strings.Split(part, ":") 77 | if len(p) != 2 { 78 | return nil, errors.New("malformed ignore string") 79 | } 80 | path := p[0] 81 | checks := strings.Split(p[1], ",") 82 | out = append(out, lint.Ignore{Pattern: path, Checks: checks}) 83 | } 84 | return out, nil 85 | } 86 | 87 | func ProcessArgs(name string, c lint.Checker, args []string) { 88 | flags := &flag.FlagSet{} 89 | flags.Usage = usage(name, flags) 90 | flags.Float64("min_confidence", 0, "Deprecated; use -ignore instead") 91 | tags := flags.String("tags", "", "List of `build tags`") 92 | ignore := flags.String("ignore", "", "Space separated list of checks to ignore, in the following format: 'import/path/file.go:Check1,Check2,...' Both the import path and file name sections support globbing, e.g. 'os/exec/*_test.go'") 93 | flags.Parse(args) 94 | 95 | ignores, err := parseIgnore(*ignore) 96 | if err != nil { 97 | fmt.Fprintln(os.Stderr, err) 98 | os.Exit(1) 99 | } 100 | runner := &runner{ 101 | checker: c, 102 | tags: strings.Fields(*tags), 103 | ignores: ignores, 104 | } 105 | paths := gotool.ImportPaths(flags.Args()) 106 | goFiles, err := runner.resolveRelative(paths) 107 | if err != nil { 108 | fmt.Fprintln(os.Stderr, err) 109 | runner.unclean = true 110 | } 111 | ctx := build.Default 112 | ctx.BuildTags = runner.tags 113 | conf := &loader.Config{ 114 | Build: &ctx, 115 | ParserMode: parser.ParseComments, 116 | } 117 | if goFiles { 118 | conf.CreateFromFilenames("adhoc", paths...) 119 | lprog, err := conf.Load() 120 | if err != nil { 121 | log.Fatal(err) 122 | } 123 | ps := runner.lint(lprog) 124 | for _, ps := range ps { 125 | for _, p := range ps { 126 | runner.unclean = true 127 | fmt.Printf("%v: %s\n", p.Position, p.Text) 128 | } 129 | } 130 | } else { 131 | conf.TypeCheckFuncBodies = func(s string) bool { 132 | for _, path := range paths { 133 | if s == path || s == path+"_test" { 134 | return true 135 | } 136 | } 137 | return false 138 | } 139 | for _, path := range paths { 140 | conf.ImportWithTests(path) 141 | } 142 | lprog, err := conf.Load() 143 | if err != nil { 144 | log.Fatal(err) 145 | } 146 | ps := runner.lint(lprog) 147 | for _, ps := range ps { 148 | for _, p := range ps { 149 | runner.unclean = true 150 | fmt.Printf("%v: %s\n", p.Position, p.Text) 151 | } 152 | 153 | } 154 | } 155 | if runner.unclean { 156 | os.Exit(1) 157 | } 158 | } 159 | 160 | func (runner *runner) lint(lprog *loader.Program) map[string][]lint.Problem { 161 | l := &lint.Linter{ 162 | Checker: runner.checker, 163 | Ignores: runner.ignores, 164 | } 165 | return l.Lint(lprog) 166 | } 167 | -------------------------------------------------------------------------------- /gosum_test.go: -------------------------------------------------------------------------------- 1 | package gosum 2 | 3 | import ( 4 | "go/ast" 5 | "go/importer" 6 | "go/parser" 7 | "go/token" 8 | "go/types" 9 | "reflect" 10 | "testing" 11 | 12 | "golang.org/x/tools/go/types/typeutil" 13 | ) 14 | 15 | func TestNewSumInterface(t *testing.T) { 16 | const src = `package self 17 | 18 | import ( 19 | "go/ast" 20 | ) 21 | 22 | type A interface { 23 | isA() 24 | } 25 | 26 | type B interface { 27 | A 28 | isB() 29 | } 30 | 31 | type C struct{} 32 | 33 | func (*C) isA() {} 34 | 35 | type B1 struct{} 36 | 37 | func (*B1) isA() {} 38 | func (*B1) isB() {} 39 | 40 | type Dep interface { 41 | ast.Node 42 | } 43 | 44 | type Empty interface {} 45 | ` 46 | 47 | fset := token.NewFileSet() 48 | f, err := parser.ParseFile(fset, "src.go", src, 0) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | conf := types.Config{Importer: importer.Default()} 53 | pkg, err := conf.Check("src", fset, []*ast.File{f}, nil) 54 | if err != nil { 55 | t.Fatal(err) // type error 56 | } 57 | 58 | pkgscope := typeutil.Dependencies(pkg) 59 | 60 | t.Run("empty scope", func(t *testing.T) { 61 | for _, name := range pkg.Scope().Names() { 62 | if obj, ok := pkg.Scope().Lookup(name).(*types.TypeName); ok && types.IsInterface(obj.Type()) { 63 | got := NewSumInterface(obj.Type().(*types.Named), nil) 64 | if got == nil { 65 | continue 66 | } 67 | if !(len(got.PkgScope) == 1 && got.PkgScope[0].Name() == "self") { 68 | t.Errorf("PkgScope should be [src], got %v", got.PkgScope) 69 | } 70 | if obj.Name() == "Dep" && got.Implements.Pointers != nil { 71 | t.Errorf("Dep: Implements.Pointers == %v, want nil", got.Implements.Pointers) 72 | } 73 | } 74 | } 75 | }) 76 | 77 | t.Run("ok", func(t *testing.T) { 78 | for _, name := range pkg.Scope().Names() { 79 | if obj, ok := pkg.Scope().Lookup(name).(*types.TypeName); ok && types.IsInterface(obj.Type()) { 80 | got := NewSumInterface(obj.Type().(*types.Named), pkgscope) 81 | // skip empty interface 82 | if got == nil { 83 | continue 84 | } 85 | switch obj.Name() { 86 | case "A": 87 | if !got.IsInternal { 88 | t.Error("interface A should be internal interface, but it's public") 89 | } 90 | if !(len(got.PkgScope) == 1 && got.PkgScope[0].Name() == "self") { 91 | t.Errorf("PkgScope should be [src], got %v", got.PkgScope) 92 | } 93 | { // SumInterface.Implements.Interfaces 94 | want := []string{"B"} 95 | gotifaces := make([]string, len(got.Implements.Interfaces)) 96 | for i, iface := range got.Implements.Interfaces { 97 | gotifaces[i] = iface.NamedInterface.Obj().Name() 98 | } 99 | if !reflect.DeepEqual(gotifaces, want) { 100 | t.Errorf("Implements.Interfaces: got %v, want %v", gotifaces, want) 101 | } 102 | } 103 | { // SumInterface.Implements.Pointers 104 | want := []string{"*src.B1", "*src.C"} 105 | gotps := make([]string, len(got.Implements.Pointers)) 106 | for i, ptr := range got.Implements.Pointers { 107 | gotps[i] = ptr.String() 108 | } 109 | if !reflect.DeepEqual(gotps, want) { 110 | t.Errorf("Implements.Pointers: got %v, want %v", gotps, want) 111 | } 112 | } 113 | case "B": 114 | if !got.IsInternal { 115 | t.Error("interface B should be internal interface, but it's public") 116 | } 117 | if !(len(got.PkgScope) == 1 && got.PkgScope[0].Name() == "self") { 118 | t.Errorf("PkgScope should be [src], got %v", got.PkgScope) 119 | } 120 | { // SumInterface.Implements.Interfaces 121 | want := []string{} 122 | gotifaces := make([]string, len(got.Implements.Interfaces)) 123 | for i, iface := range got.Implements.Interfaces { 124 | gotifaces[i] = iface.NamedInterface.Obj().Name() 125 | } 126 | if !reflect.DeepEqual(gotifaces, want) { 127 | t.Errorf("Implements.Interfaces: got %v, want %v", gotifaces, want) 128 | } 129 | } 130 | { // SumInterface.Implements.Pointers 131 | want := []string{"*src.B1"} 132 | gotps := make([]string, len(got.Implements.Pointers)) 133 | for i, ptr := range got.Implements.Pointers { 134 | gotps[i] = ptr.String() 135 | } 136 | if !reflect.DeepEqual(gotps, want) { 137 | t.Errorf("Implements.Pointers: got %v, want %v", gotps, want) 138 | } 139 | } 140 | case "Dep": 141 | if got.IsInternal { 142 | t.Error("interface Dep should be public interface, but it's internal") 143 | } 144 | if !reflect.DeepEqual(got.PkgScope, pkgscope) { 145 | t.Errorf("PkgScope should be %v, got %v", pkgscope, got.PkgScope) 146 | } 147 | covered := make(map[string]bool) 148 | for _, iface := range got.Implements.Interfaces { 149 | for _, ptr := range iface.Implements.Pointers { 150 | covered[ptr.String()] = true 151 | } 152 | } 153 | if len(covered) != len(got.Implements.Pointers) { 154 | msg := "all covered Pointer by each Implements.Interfaces should be same with Implements.Pointers:\n got %v\nwant %v" 155 | t.Errorf(msg, covered, got.Implements.Pointers) 156 | } 157 | default: 158 | t.Errorf("got unexpected interface: %v", obj) 159 | } 160 | } 161 | } 162 | }) 163 | 164 | } 165 | 166 | func TestIsInternalInterface(t *testing.T) { 167 | const src = `package isinternal 168 | 169 | type publicInterface interface { 170 | Public() 171 | } 172 | 173 | type publicInterface2 interface { 174 | publicInterface 175 | } 176 | 177 | type internalInterface1 interface { 178 | internal() 179 | } 180 | 181 | type internalInterface2 interface { 182 | Public() 183 | internal() 184 | } 185 | 186 | type internalInterface3 interface { 187 | publicInterface 188 | internal() 189 | } 190 | ` 191 | 192 | fset := token.NewFileSet() 193 | f, err := parser.ParseFile(fset, "src.go", src, 0) 194 | if err != nil { 195 | t.Fatal(err) 196 | } 197 | conf := types.Config{Importer: importer.Default()} 198 | pkg, err := conf.Check("src", fset, []*ast.File{f}, nil) 199 | if err != nil { 200 | t.Fatal(err) // type error 201 | } 202 | 203 | wants := map[string]bool{ 204 | "publicInterface": false, 205 | "publicInterface2": false, 206 | "internalInterface1": true, 207 | "internalInterface2": true, 208 | "internalInterface3": true, 209 | } 210 | 211 | for _, name := range pkg.Scope().Names() { 212 | if obj, ok := pkg.Scope().Lookup(name).(*types.TypeName); ok && types.IsInterface(obj.Type()) { 213 | typ := obj.Type().Underlying().(*types.Interface) 214 | got := IsInternalInterface(typ) 215 | want, ok := wants[name] 216 | if !ok { 217 | t.Errorf("IsInternalInterface(%s) == %v, want is not prepared", name, got) 218 | } else if got != want { 219 | t.Errorf("IsInternalInterface(%s) == %v, want %v", name, got, want) 220 | } 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /vendor/honnef.co/go/lint/lint.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 The Go Authors. All rights reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file or at 5 | // https://developers.google.com/open-source/licenses/bsd. 6 | 7 | // Package lint provides the foundation for tools like gosimple. 8 | package lint // import "honnef.co/go/lint" 9 | 10 | import ( 11 | "bytes" 12 | "fmt" 13 | "go/ast" 14 | "go/constant" 15 | "go/printer" 16 | "go/token" 17 | "go/types" 18 | "io/ioutil" 19 | "path/filepath" 20 | "sort" 21 | "strings" 22 | 23 | "golang.org/x/tools/go/ast/astutil" 24 | "golang.org/x/tools/go/loader" 25 | "golang.org/x/tools/go/ssa" 26 | ) 27 | 28 | type Ignore struct { 29 | Pattern string 30 | Checks []string 31 | } 32 | 33 | type Program struct { 34 | Prog *loader.Program 35 | Packages []*Pkg 36 | } 37 | 38 | type Func func(*File) 39 | 40 | // Problem represents a problem in some source code. 41 | type Problem struct { 42 | Position token.Position // position in source file 43 | Text string // the prose that describes the problem 44 | 45 | // If the problem has a suggested fix (the minority case), 46 | // ReplacementLine is a full replacement for the relevant line of the source file. 47 | ReplacementLine string 48 | } 49 | 50 | func (p *Problem) String() string { 51 | return p.Text 52 | } 53 | 54 | type ByPosition []Problem 55 | 56 | func (p ByPosition) Len() int { return len(p) } 57 | func (p ByPosition) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 58 | 59 | func (p ByPosition) Less(i, j int) bool { 60 | pi, pj := p[i].Position, p[j].Position 61 | 62 | if pi.Filename != pj.Filename { 63 | return pi.Filename < pj.Filename 64 | } 65 | if pi.Line != pj.Line { 66 | return pi.Line < pj.Line 67 | } 68 | if pi.Column != pj.Column { 69 | return pi.Column < pj.Column 70 | } 71 | 72 | return p[i].Text < p[j].Text 73 | } 74 | 75 | type Checker interface { 76 | Init(*Program) 77 | Funcs() map[string]Func 78 | } 79 | 80 | // A Linter lints Go source code. 81 | type Linter struct { 82 | Checker Checker 83 | Ignores []Ignore 84 | } 85 | 86 | func buildPackage(pkg *types.Package, files []*ast.File, info *types.Info, fset *token.FileSet, mode ssa.BuilderMode) *ssa.Package { 87 | prog := ssa.NewProgram(fset, mode) 88 | 89 | // Create SSA packages for all imports. 90 | // Order is not significant. 91 | created := make(map[*types.Package]bool) 92 | var createAll func(pkgs []*types.Package) 93 | createAll = func(pkgs []*types.Package) { 94 | for _, p := range pkgs { 95 | if !created[p] { 96 | created[p] = true 97 | prog.CreatePackage(p, nil, nil, true) 98 | createAll(p.Imports()) 99 | } 100 | } 101 | } 102 | createAll(pkg.Imports()) 103 | 104 | // Create and build the primary package. 105 | ssapkg := prog.CreatePackage(pkg, files, info, false) 106 | ssapkg.Build() 107 | return ssapkg 108 | } 109 | 110 | func (l *Linter) ignore(f *File, check string) bool { 111 | for _, ig := range l.Ignores { 112 | pkg := f.Pkg.TypesPkg.Path() 113 | if strings.HasSuffix(pkg, "_test") { 114 | pkg = pkg[:len(pkg)-len("_test")] 115 | } 116 | name := filepath.Join(pkg, filepath.Base(f.Filename)) 117 | if m, _ := filepath.Match(ig.Pattern, name); !m { 118 | continue 119 | } 120 | for _, c := range ig.Checks { 121 | if m, _ := filepath.Match(c, check); m { 122 | return true 123 | } 124 | } 125 | } 126 | return false 127 | } 128 | 129 | func (l *Linter) Lint(lprog *loader.Program) map[string][]Problem { 130 | var pkgs []*Pkg 131 | for _, pkginfo := range lprog.InitialPackages() { 132 | ssapkg := buildPackage(pkginfo.Pkg, pkginfo.Files, &pkginfo.Info, lprog.Fset, ssa.GlobalDebug) 133 | pkg := &Pkg{ 134 | TypesPkg: pkginfo.Pkg, 135 | TypesInfo: pkginfo.Info, 136 | SSAPkg: ssapkg, 137 | PkgInfo: pkginfo, 138 | } 139 | pkgs = append(pkgs, pkg) 140 | } 141 | prog := &Program{ 142 | Prog: lprog, 143 | Packages: pkgs, 144 | } 145 | l.Checker.Init(prog) 146 | 147 | funcs := l.Checker.Funcs() 148 | var keys []string 149 | for k := range funcs { 150 | keys = append(keys, k) 151 | } 152 | sort.Strings(keys) 153 | 154 | out := map[string][]Problem{} 155 | for _, pkg := range pkgs { 156 | pkginfo := pkg.PkgInfo 157 | for _, file := range pkginfo.Files { 158 | path := lprog.Fset.Position(file.Pos()).Filename 159 | for _, k := range keys { 160 | f := &File{ 161 | Pkg: pkg, 162 | File: file, 163 | Filename: path, 164 | Fset: lprog.Fset, 165 | Program: lprog, 166 | check: k, 167 | } 168 | 169 | fn := funcs[k] 170 | if fn == nil { 171 | continue 172 | } 173 | if l.ignore(f, k) { 174 | continue 175 | } 176 | fn(f) 177 | } 178 | } 179 | sort.Sort(ByPosition(pkg.problems)) 180 | out[pkginfo.Pkg.Path()] = pkg.problems 181 | } 182 | return out 183 | } 184 | 185 | func (f *File) Source() []byte { 186 | if f.src != nil { 187 | return f.src 188 | } 189 | path := f.Fset.Position(f.File.Pos()).Filename 190 | if path != "" { 191 | f.src, _ = ioutil.ReadFile(path) 192 | } 193 | return f.src 194 | } 195 | 196 | // pkg represents a package being linted. 197 | type Pkg struct { 198 | TypesPkg *types.Package 199 | TypesInfo types.Info 200 | SSAPkg *ssa.Package 201 | PkgInfo *loader.PackageInfo 202 | 203 | problems []Problem 204 | } 205 | 206 | // file represents a file being linted. 207 | type File struct { 208 | Pkg *Pkg 209 | File *ast.File 210 | Filename string 211 | Fset *token.FileSet 212 | Program *loader.Program 213 | src []byte 214 | check string 215 | } 216 | 217 | func (f *File) IsTest() bool { return strings.HasSuffix(f.Filename, "_test.go") } 218 | 219 | type Positioner interface { 220 | Pos() token.Pos 221 | } 222 | 223 | func (f *File) Errorf(n Positioner, format string, args ...interface{}) *Problem { 224 | pos := f.Fset.Position(n.Pos()) 225 | return f.Pkg.errorfAt(pos, f.check, format, args...) 226 | } 227 | 228 | func (p *Pkg) errorfAt(pos token.Position, check string, format string, args ...interface{}) *Problem { 229 | problem := Problem{ 230 | Position: pos, 231 | } 232 | 233 | problem.Text = fmt.Sprintf(format, args...) + fmt.Sprintf(" (%s)", check) 234 | p.problems = append(p.problems, problem) 235 | return &p.problems[len(p.problems)-1] 236 | } 237 | 238 | func (p *Pkg) IsNamedType(typ types.Type, importPath, name string) bool { 239 | n, ok := typ.(*types.Named) 240 | if !ok { 241 | return false 242 | } 243 | tn := n.Obj() 244 | return tn != nil && tn.Pkg() != nil && tn.Pkg().Path() == importPath && tn.Name() == name 245 | } 246 | 247 | func (f *File) IsMain() bool { 248 | return f.File.Name.Name == "main" 249 | } 250 | 251 | // exportedType reports whether typ is an exported type. 252 | // It is imprecise, and will err on the side of returning true, 253 | // such as for composite types. 254 | func ExportedType(typ types.Type) bool { 255 | switch T := typ.(type) { 256 | case *types.Named: 257 | // Builtin types have no package. 258 | return T.Obj().Pkg() == nil || T.Obj().Exported() 259 | case *types.Map: 260 | return ExportedType(T.Key()) && ExportedType(T.Elem()) 261 | case interface { 262 | Elem() types.Type 263 | }: // array, slice, pointer, chan 264 | return ExportedType(T.Elem()) 265 | } 266 | // Be conservative about other types, such as struct, interface, etc. 267 | return true 268 | } 269 | 270 | func ReceiverType(fn *ast.FuncDecl) string { 271 | switch e := fn.Recv.List[0].Type.(type) { 272 | case *ast.Ident: 273 | return e.Name 274 | case *ast.StarExpr: 275 | return e.X.(*ast.Ident).Name 276 | } 277 | panic(fmt.Sprintf("unknown method receiver AST node type %T", fn.Recv.List[0].Type)) 278 | } 279 | 280 | func (f *File) Walk(fn func(ast.Node) bool) { 281 | ast.Inspect(f.File, fn) 282 | } 283 | 284 | func (f *File) Render(x interface{}) string { 285 | var buf bytes.Buffer 286 | if err := printer.Fprint(&buf, f.Fset, x); err != nil { 287 | panic(err) 288 | } 289 | return buf.String() 290 | } 291 | 292 | func (f *File) RenderArgs(args []ast.Expr) string { 293 | var ss []string 294 | for _, arg := range args { 295 | ss = append(ss, f.Render(arg)) 296 | } 297 | return strings.Join(ss, ", ") 298 | } 299 | 300 | func IsIdent(expr ast.Expr, ident string) bool { 301 | id, ok := expr.(*ast.Ident) 302 | return ok && id.Name == ident 303 | } 304 | 305 | // isBlank returns whether id is the blank identifier "_". 306 | // If id == nil, the answer is false. 307 | func IsBlank(id ast.Expr) bool { 308 | ident, ok := id.(*ast.Ident) 309 | return ok && ident.Name == "_" 310 | } 311 | 312 | func IsPkgDot(expr ast.Expr, pkg, name string) bool { 313 | sel, ok := expr.(*ast.SelectorExpr) 314 | return ok && IsIdent(sel.X, pkg) && IsIdent(sel.Sel, name) 315 | } 316 | 317 | func IsZero(expr ast.Expr) bool { 318 | lit, ok := expr.(*ast.BasicLit) 319 | return ok && lit.Kind == token.INT && lit.Value == "0" 320 | } 321 | 322 | func IsOne(expr ast.Expr) bool { 323 | lit, ok := expr.(*ast.BasicLit) 324 | return ok && lit.Kind == token.INT && lit.Value == "1" 325 | } 326 | 327 | func IsNil(expr ast.Expr) bool { 328 | // FIXME(dominikh): use type information 329 | id, ok := expr.(*ast.Ident) 330 | return ok && id.Name == "nil" 331 | } 332 | 333 | var basicTypeKinds = map[types.BasicKind]string{ 334 | types.UntypedBool: "bool", 335 | types.UntypedInt: "int", 336 | types.UntypedRune: "rune", 337 | types.UntypedFloat: "float64", 338 | types.UntypedComplex: "complex128", 339 | types.UntypedString: "string", 340 | } 341 | 342 | // isUntypedConst reports whether expr is an untyped constant, 343 | // and indicates what its default type is. 344 | // scope may be nil. 345 | func (f *File) IsUntypedConst(expr ast.Expr) (defType string, ok bool) { 346 | // Re-evaluate expr outside of its context to see if it's untyped. 347 | // (An expr evaluated within, for example, an assignment context will get the type of the LHS.) 348 | exprStr := f.Render(expr) 349 | tv, err := types.Eval(f.Fset, f.Pkg.TypesPkg, expr.Pos(), exprStr) 350 | if err != nil { 351 | return "", false 352 | } 353 | if b, ok := tv.Type.(*types.Basic); ok { 354 | if dt, ok := basicTypeKinds[b.Kind()]; ok { 355 | return dt, true 356 | } 357 | } 358 | 359 | return "", false 360 | } 361 | 362 | func (f *File) BoolConst(expr ast.Expr) bool { 363 | val := f.Pkg.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val() 364 | return constant.BoolVal(val) 365 | } 366 | 367 | func (f *File) IsBoolConst(expr ast.Expr) bool { 368 | // We explicitly don't support typed bools because more often than 369 | // not, custom bool types are used as binary enums and the 370 | // explicit comparison is desired. 371 | 372 | ident, ok := expr.(*ast.Ident) 373 | if !ok { 374 | return false 375 | } 376 | obj := f.Pkg.TypesInfo.ObjectOf(ident) 377 | c, ok := obj.(*types.Const) 378 | if !ok { 379 | return false 380 | } 381 | basic, ok := c.Type().(*types.Basic) 382 | if !ok { 383 | return false 384 | } 385 | if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool { 386 | return false 387 | } 388 | return true 389 | } 390 | 391 | func ExprToInt(expr ast.Expr) (string, bool) { 392 | switch y := expr.(type) { 393 | case *ast.BasicLit: 394 | if y.Kind != token.INT { 395 | return "", false 396 | } 397 | return y.Value, true 398 | case *ast.UnaryExpr: 399 | if y.Op != token.SUB && y.Op != token.ADD { 400 | return "", false 401 | } 402 | x, ok := y.X.(*ast.BasicLit) 403 | if !ok { 404 | return "", false 405 | } 406 | if x.Kind != token.INT { 407 | return "", false 408 | } 409 | v := constant.MakeFromLiteral(x.Value, x.Kind, 0) 410 | return constant.UnaryOp(y.Op, v, 0).String(), true 411 | default: 412 | return "", false 413 | } 414 | } 415 | 416 | func (f *File) EnclosingSSAFunction(node Positioner) *ssa.Function { 417 | path, _ := astutil.PathEnclosingInterval(f.File, node.Pos(), node.Pos()) 418 | return ssa.EnclosingFunction(f.Pkg.SSAPkg, path) 419 | } 420 | --------------------------------------------------------------------------------