├── cmd └── noticeme │ ├── .gitignore │ └── main.go ├── go.mod ├── testdata └── src │ └── x │ └── x.go ├── noticeme_test.go ├── README.md ├── noticeme.go └── go.sum /cmd/noticeme/.gitignore: -------------------------------------------------------------------------------- 1 | /noticeme 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/molecula/noticeme 2 | 3 | go 1.13 4 | 5 | require golang.org/x/tools v0.16.1 6 | -------------------------------------------------------------------------------- /testdata/src/x/x.go: -------------------------------------------------------------------------------- 1 | package x 2 | 3 | func x() int { 4 | return 0 5 | } 6 | 7 | func directCall() { 8 | x() // want `unused value of type int` 9 | } 10 | 11 | func directCallUsing() int { 12 | return x() 13 | } 14 | 15 | var xMap = map[string]func() int{"foo": x} 16 | 17 | func unusedMap() { 18 | xMap["foo"]() // want `unused value of type int` 19 | } 20 | 21 | func usedMap() int { 22 | return xMap["foo"]() 23 | } 24 | -------------------------------------------------------------------------------- /cmd/noticeme/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Pilosa Corp. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "github.com/molecula/noticeme" 19 | "golang.org/x/tools/go/analysis/singlechecker" 20 | ) 21 | 22 | func main() { 23 | singlechecker.Main(noticeme.Analyzer) 24 | } 25 | -------------------------------------------------------------------------------- /noticeme_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Pilosa Corp. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package noticeme_test 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/molecula/noticeme" 21 | "golang.org/x/tools/go/analysis/analysistest" 22 | ) 23 | 24 | func TestNoticeMe(t *testing.T) { 25 | testdata := analysistest.TestData() 26 | noticeme.Analyzer.Flags.Parse([]string{"-types", "int"}) 27 | analysistest.Run(t, testdata, noticeme.Analyzer, "x") 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Notice Me 2 | 3 | Notice Me is a static analysis tool for Go programs that can identify unused 4 | values, typically function call results. This is similar to the `errcheck` 5 | tool, but instead of checking for unused results of type `error`, it checks 6 | for unused results of whatever types you specify. There's also some overlap 7 | with the `unusedresult` example analysis pass. 8 | 9 | ### Building 10 | 11 | To build, run `go build` in `cmd/noticeme`. The `noticeme` tool should work 12 | with any reasonably-recent version of Go. It relies on the `golang.org/x/tools` 13 | package, but shouldn't care about the specific version. 14 | 15 | ### Usage 16 | 17 | To check the package in the current directory for any and all unused 18 | values of type `error`: 19 | 20 | noticeme -types error . 21 | 22 | Note that this will be pickier than other utilities -- for instance, 23 | `fmt.Printf` returns an error which is almost never checked. There's no 24 | hooks for disabling these warnings case-by-case right now. To specify 25 | additional types, use a comma-separated list. So for instance, the motive 26 | for developing this involved the usage of `*Container` objects in our 27 | Roaring bitmap implementation: 28 | 29 | noticeme -types "*roaring.Container" . 30 | 31 | Type names are checked with full import path qualifiers, package qualifiers 32 | only, and unqualified. Note that these qualifiers are applied to all the 33 | types in a line at once, so a type like this won't work: 34 | 35 | map[package.Foo]github.com/user/repo.Bar 36 | 37 | ### Limitations 38 | 39 | The design here is to assume that the type of an expression which exists 40 | as a standalone "expression statement" has not been "used". If you assign 41 | a value, or use it in another expression, then it's being used. You may be 42 | able to bypass this analysis. However, it does handle fairly arbitrary 43 | expressions, not just direct function or method calls. 44 | 45 | There is no guessing about pointers and non-pointers; if you specify a 46 | type of `foo` as important, functions returning `*foo` are not special, and 47 | vise versa. 48 | 49 | Nested types are not recognized, but tuples (such as functions with multiple 50 | return values) are handled. 51 | 52 | ### References 53 | 54 | * [errcheck](https://github.com/kisielk/errcheck) 55 | * [unusedresult](https://godoc.org/golang.org/x/tools/go/analysis/passes/unusedresult) 56 | -------------------------------------------------------------------------------- /noticeme.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Molecula Corp. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package noticeme 16 | 17 | import ( 18 | "errors" 19 | "go/ast" 20 | "go/types" 21 | "strings" 22 | "sync" 23 | 24 | "golang.org/x/tools/go/analysis" 25 | "golang.org/x/tools/go/analysis/passes/inspect" 26 | "golang.org/x/tools/go/ast/inspector" 27 | ) 28 | 29 | const Doc = `check for unused values by type 30 | 31 | Invoke with -types , with comma-separated types. Names are 32 | checked against the name with no qualifier, just a package name, and the 33 | full import path. Reports any expression statements that have one or more 34 | of the given types. 35 | ` 36 | 37 | var Analyzer = &analysis.Analyzer{ 38 | Name: "noticeme", 39 | Doc: Doc, 40 | Requires: []*analysis.Analyzer{inspect.Analyzer}, 41 | Run: noticeme, 42 | } 43 | 44 | var importantTypes string 45 | 46 | func init() { 47 | Analyzer.Flags.StringVar(&importantTypes, "types", "", "specify important types") 48 | } 49 | 50 | type typeList []string 51 | 52 | func typeNameOnly(pkg *types.Package) string { 53 | return "" 54 | } 55 | 56 | func packageNameOnly(pkg *types.Package) string { 57 | return pkg.Name() 58 | } 59 | 60 | // matchImportance returns the first string in the list it found which 61 | // matches the last component of the name of the given type, or of a type 62 | // within it if it's a tuple. 63 | func (tl typeList) matchImportance(t types.Type) (bool, string) { 64 | if tuple, ok := t.(*types.Tuple); ok { 65 | for i := 0; i < tuple.Len(); i++ { 66 | subType := tuple.At(i).Type() 67 | important, why := tl.matchImportance(subType) 68 | if important { 69 | return important, why 70 | } 71 | } 72 | } 73 | names := []string{ 74 | types.TypeString(t, typeNameOnly), 75 | types.TypeString(t, packageNameOnly), 76 | types.TypeString(t, nil), 77 | } 78 | for _, w := range tl { 79 | for _, name := range names { 80 | if name == w { 81 | return true, name 82 | } 83 | } 84 | } 85 | return false, "" 86 | } 87 | 88 | var parseImportant sync.Once 89 | var importantList typeList 90 | var relevantTypes = map[types.Type]string{} 91 | 92 | func noticeme(pass *analysis.Pass) (_ interface{}, err error) { 93 | parseImportant.Do(func() { 94 | if importantTypes == "" { 95 | err = errors.New("you must specify which types you care about (-types)") 96 | return 97 | } 98 | importantList = strings.Split(importantTypes, ",") 99 | }) 100 | if err != nil { 101 | return nil, err 102 | } 103 | inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 104 | nodeFilter := []ast.Node{(*ast.ExprStmt)(nil)} 105 | inspect.Preorder(nodeFilter, func(n ast.Node) { 106 | expr := n.(*ast.ExprStmt).X 107 | exprType := pass.TypesInfo.Types[expr].Type 108 | relevant, ok := relevantTypes[exprType] 109 | if !ok { 110 | _, relevant = importantList.matchImportance(exprType) 111 | relevantTypes[exprType] = relevant 112 | } 113 | if relevant != "" { 114 | pass.Reportf(n.Pos(), "unused value of type %s", relevant) 115 | } 116 | }) 117 | return nil, nil 118 | } 119 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 2 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 3 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 4 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 5 | golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 6 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 7 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 8 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 9 | golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= 10 | golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 11 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 12 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 13 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 14 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 15 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 16 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 17 | golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 18 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 19 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 20 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 21 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 22 | golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= 23 | golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 24 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 25 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 26 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 27 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 28 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 29 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 30 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 31 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 32 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 33 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 34 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 35 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 36 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 37 | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 38 | golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= 39 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 40 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 41 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 42 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 43 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 44 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 45 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 46 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 47 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 48 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 49 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 50 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 51 | golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= 52 | golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= 53 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 54 | --------------------------------------------------------------------------------