Fix lint errs (#59)
This commit is contained in:
48
vendor/honnef.co/go/tools/arg/arg.go
vendored
Normal file
48
vendor/honnef.co/go/tools/arg/arg.go
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
package arg
|
||||
|
||||
var args = map[string]int{
|
||||
"(*encoding/json.Decoder).Decode.v": 0,
|
||||
"(*encoding/json.Encoder).Encode.v": 0,
|
||||
"(*encoding/xml.Decoder).Decode.v": 0,
|
||||
"(*encoding/xml.Encoder).Encode.v": 0,
|
||||
"(*sync.Pool).Put.x": 0,
|
||||
"(*text/template.Template).Parse.text": 0,
|
||||
"(io.Seeker).Seek.offset": 0,
|
||||
"(time.Time).Sub.u": 0,
|
||||
"append.elems": 1,
|
||||
"append.slice": 0,
|
||||
"bytes.Equal.a": 0,
|
||||
"bytes.Equal.b": 1,
|
||||
"encoding/binary.Write.data": 2,
|
||||
"errors.New.text": 0,
|
||||
"fmt.Fprintf.format": 1,
|
||||
"fmt.Printf.format": 0,
|
||||
"fmt.Sprintf.a[0]": 1,
|
||||
"fmt.Sprintf.format": 0,
|
||||
"json.Marshal.v": 0,
|
||||
"json.Unmarshal.v": 1,
|
||||
"len.v": 0,
|
||||
"make.size[0]": 1,
|
||||
"make.size[1]": 2,
|
||||
"make.t": 0,
|
||||
"net/url.Parse.rawurl": 0,
|
||||
"os.OpenFile.flag": 1,
|
||||
"os/exec.Command.name": 0,
|
||||
"os/signal.Notify.c": 0,
|
||||
"regexp.Compile.expr": 0,
|
||||
"runtime.SetFinalizer.finalizer": 1,
|
||||
"runtime.SetFinalizer.obj": 0,
|
||||
"sort.Sort.data": 0,
|
||||
"time.Parse.layout": 0,
|
||||
"time.Sleep.d": 0,
|
||||
"xml.Marshal.v": 0,
|
||||
"xml.Unmarshal.v": 1,
|
||||
}
|
||||
|
||||
func Arg(name string) int {
|
||||
n, ok := args[name]
|
||||
if !ok {
|
||||
panic("unknown argument " + name)
|
||||
}
|
||||
return n
|
||||
}
|
15
vendor/honnef.co/go/tools/cmd/gosimple/README.md
vendored
15
vendor/honnef.co/go/tools/cmd/gosimple/README.md
vendored
@ -1,15 +0,0 @@
|
||||
# gosimple
|
||||
|
||||
_gosimple_ is a linter for Go source code that specialises on
|
||||
simplifying code.
|
||||
|
||||
## Installation
|
||||
|
||||
Gosimple requires Go 1.6 or later.
|
||||
|
||||
go get honnef.co/go/tools/cmd/gosimple
|
||||
|
||||
## Documentation
|
||||
|
||||
Detailed documentation can be found on
|
||||
[staticcheck.io](https://staticcheck.io/docs/gosimple).
|
@ -1,21 +0,0 @@
|
||||
// gosimple detects code that could be rewritten in a simpler way.
|
||||
package main // import "honnef.co/go/tools/cmd/gosimple"
|
||||
import (
|
||||
"os"
|
||||
|
||||
"honnef.co/go/tools/lint/lintutil"
|
||||
"honnef.co/go/tools/simple"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fs := lintutil.FlagSet("gosimple")
|
||||
gen := fs.Bool("generated", false, "Check generated code")
|
||||
fs.Parse(os.Args[1:])
|
||||
c := simple.NewChecker()
|
||||
c.CheckGenerated = *gen
|
||||
cfg := lintutil.CheckerConfig{
|
||||
Checker: c,
|
||||
ExitNonZero: true,
|
||||
}
|
||||
lintutil.ProcessFlagSet([]lintutil.CheckerConfig{cfg}, fs)
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
# staticcheck
|
||||
|
||||
_staticcheck_ is `go vet` on steroids, applying a ton of static analysis
|
||||
checks you might be used to from tools like ReSharper for C#.
|
||||
_staticcheck_ offers extensive analysis of Go code, covering a myriad
|
||||
of categories. It will detect bugs, suggest code simplifications,
|
||||
point out dead code, and more.
|
||||
|
||||
## Installation
|
||||
|
||||
Staticcheck requires Go 1.6 or later.
|
||||
|
||||
go get honnef.co/go/tools/cmd/staticcheck
|
||||
See [the main README](https://github.com/dominikh/go-tools#installation) for installation instructions.
|
||||
|
||||
## Documentation
|
||||
|
||||
Detailed documentation can be found on
|
||||
[staticcheck.io](https://staticcheck.io/docs/staticcheck).
|
||||
[staticcheck.io](https://staticcheck.io/docs/).
|
||||
|
||||
|
@ -1,23 +1,30 @@
|
||||
// staticcheck detects a myriad of bugs and inefficiencies in your
|
||||
// code.
|
||||
// staticcheck analyses Go code and makes it better.
|
||||
package main // import "honnef.co/go/tools/cmd/staticcheck"
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"honnef.co/go/tools/lint"
|
||||
"honnef.co/go/tools/lint/lintutil"
|
||||
"honnef.co/go/tools/simple"
|
||||
"honnef.co/go/tools/staticcheck"
|
||||
"honnef.co/go/tools/stylecheck"
|
||||
"honnef.co/go/tools/unused"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fs := lintutil.FlagSet("staticcheck")
|
||||
gen := fs.Bool("generated", false, "Check generated code")
|
||||
fs.Parse(os.Args[1:])
|
||||
c := staticcheck.NewChecker()
|
||||
c.CheckGenerated = *gen
|
||||
cfg := lintutil.CheckerConfig{
|
||||
Checker: c,
|
||||
ExitNonZero: true,
|
||||
|
||||
checkers := []lint.Checker{
|
||||
simple.NewChecker(),
|
||||
staticcheck.NewChecker(),
|
||||
stylecheck.NewChecker(),
|
||||
}
|
||||
lintutil.ProcessFlagSet([]lintutil.CheckerConfig{cfg}, fs)
|
||||
|
||||
uc := unused.NewChecker(unused.CheckAll)
|
||||
uc.ConsiderReflection = true
|
||||
checkers = append(checkers, unused.NewLintChecker(uc))
|
||||
|
||||
lintutil.ProcessFlagSet(checkers, fs)
|
||||
}
|
||||
|
131
vendor/honnef.co/go/tools/cmd/unused/README.md
vendored
131
vendor/honnef.co/go/tools/cmd/unused/README.md
vendored
@ -1,131 +0,0 @@
|
||||
# unused
|
||||
|
||||
_unused_ checks Go code for unused constants, variables, functions and
|
||||
types.
|
||||
|
||||
## Install
|
||||
|
||||
go get honnef.co/go/tools/cmd/unused
|
||||
|
||||
## Usage
|
||||
|
||||
unused -help
|
||||
|
||||
## Usage Tips
|
||||
|
||||
- When running _unused_ on multiple packages, it will first try to
|
||||
check them all at once, because that's faster. If any of the
|
||||
packages don't compile, however, _unused_ will check each package
|
||||
individually.
|
||||
|
||||
The first step can, depending on the number of packages, use a lot
|
||||
of memory. For the entire standard library, it uses roughly 800 MB.
|
||||
For a GOPATH with thousands of packages, it can quickly use several
|
||||
gigabytes. If that is an issue, consider using something like this
|
||||
instead:
|
||||
|
||||
```
|
||||
for pkg in $(go list your_selection); do unused "$pkg"; done
|
||||
```
|
||||
|
||||
This will effectively skip the first step and always check every
|
||||
package individually.
|
||||
|
||||
## What counts as used/unused?
|
||||
|
||||
_unused_ checks for unused constants, functions, types and optionally
|
||||
struct fields. They will be considered used or unused under the
|
||||
following conditions:
|
||||
|
||||
- Unexported package-level objects will be reported as unused if there
|
||||
are no explicit references to them.
|
||||
|
||||
- Unexported methods will be reported as unused if there are no
|
||||
explicit references to them and if they don't implement any
|
||||
interfaces.
|
||||
|
||||
- The `main` function is considered as used if it's in the `main`
|
||||
package.
|
||||
|
||||
- `init` functions are always considered as used.
|
||||
|
||||
- Exported objects in function scope are treated like unexported
|
||||
objects.
|
||||
|
||||
- Exported functions in tests are treated like unexported functions,
|
||||
unless they're test, benchmark or example functions.
|
||||
|
||||
- Struct fields will be considered as unused if there are no explicit
|
||||
references to them. Unkeyed composite literals with >=1 elements
|
||||
mark all fields of the struct as used.
|
||||
|
||||
- Neither the checks for methods nor for struct fields are aware of
|
||||
the reflect package and may thus produce false positives.
|
||||
|
||||
## Whole program analysis
|
||||
|
||||
Optionally via the `-exported` flag, _unused_ can analyse all
|
||||
arguments as a single program and report unused exported identifiers.
|
||||
This can be useful for checking "internal" packages, or large software
|
||||
projects that do not export an API to the public, but use exported
|
||||
methods between components.
|
||||
|
||||
Do note that in the whole-program analysis, all arguments must
|
||||
type-check. It is not possible to check packages individually in this
|
||||
mode.
|
||||
|
||||
## Examples
|
||||
|
||||
```
|
||||
$ time unused cmd/go
|
||||
/usr/lib/go/src/cmd/go/build.go:1327:6: func hasString is unused
|
||||
/usr/lib/go/src/cmd/go/build.go:2328:6: func toolVerify is unused
|
||||
/usr/lib/go/src/cmd/go/generate.go:375:21: func identLength is unused
|
||||
/usr/lib/go/src/cmd/go/get.go:474:5: var goTag is unused
|
||||
/usr/lib/go/src/cmd/go/get.go:513:6: func cmpGoVersion is unused
|
||||
/usr/lib/go/src/cmd/go/go_test.go:426:23: func grepCountStdout is unused
|
||||
/usr/lib/go/src/cmd/go/go_test.go:432:23: func grepCountStderr is unused
|
||||
/usr/lib/go/src/cmd/go/main.go:406:5: var logf is unused
|
||||
/usr/lib/go/src/cmd/go/main.go:431:6: func runOut is unused
|
||||
/usr/lib/go/src/cmd/go/pkg.go:91:2: field forceBuild is unused
|
||||
/usr/lib/go/src/cmd/go/pkg.go:688:2: const toRoot is unused
|
||||
/usr/lib/go/src/cmd/go/testflag.go:278:6: func setIntFlag is unused
|
||||
unused cmd/go 3.33s user 0.25s system 447% cpu 0.799 total
|
||||
```
|
||||
|
||||
```
|
||||
$ time unused $(go list github.com/prometheus/prometheus/... | grep -v /vendor/)
|
||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/promql/engine_test.go:11:5: var noop is unused
|
||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/discovery/dns.go:39:2: const interval is unused
|
||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/discovery/dns.go:69:2: field m is unused
|
||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/discovery/nerve.go:31:2: const nerveNodePrefix is unused
|
||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/discovery/serverset.go:33:2: const serversetNodePrefix is unused
|
||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/scrape.go:41:2: const ingestedSamplesCap is unused
|
||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/scrape.go:49:2: var errSkippedScrape is unused
|
||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/targetmanager.go:184:2: field providers is unused
|
||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/storage/local/delta.go:394:2: field error is unused
|
||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/storage/local/delta.go:398:3: field error is unused
|
||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/storage/local/doubledelta.go:500:2: field error is unused
|
||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/storage/local/doubledelta.go:504:3: field error is unused
|
||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/storage/remote/opentsdb/client.go:40:2: var illegalCharsRE is unused
|
||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/util/stats/timer.go:56:2: field child is unused
|
||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/util/treecache/treecache.go:25:2: field zkEvents is unused
|
||||
unused $(go list github.com/prometheus/prometheus/... | grep -v /vendor/) 5.70s user 0.43s system 535% cpu 1.142 total
|
||||
```
|
||||
|
||||
```
|
||||
$ time unused -exported github.com/kr/pretty/...
|
||||
/home/dominikh/prj/src/github.com/kr/pretty/formatter.go:14:2: const limit is unused
|
||||
/home/dominikh/prj/src/github.com/kr/pretty/formatter.go:322:6: func tryDeepEqual is unused
|
||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:20:6: func Errorf is unused
|
||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:28:6: func Fprintf is unused
|
||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:37:6: func Log is unused
|
||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:45:6: func Logf is unused
|
||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:54:6: func Logln is unused
|
||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:63:6: func Print is unused
|
||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:71:6: func Printf is unused
|
||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:80:6: func Println is unused
|
||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:88:6: func Sprintf is unused
|
||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:92:6: func wrap is unused
|
||||
unused -exported github.com/kr/pretty/... 1.23s user 0.19s system 253% cpu 0.558 total
|
||||
```
|
78
vendor/honnef.co/go/tools/cmd/unused/main.go
vendored
78
vendor/honnef.co/go/tools/cmd/unused/main.go
vendored
@ -1,78 +0,0 @@
|
||||
// unused reports unused identifiers (types, functions, ...) in your
|
||||
// code.
|
||||
package main // import "honnef.co/go/tools/cmd/unused"
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"honnef.co/go/tools/lint/lintutil"
|
||||
"honnef.co/go/tools/unused"
|
||||
)
|
||||
|
||||
var (
|
||||
fConstants bool
|
||||
fFields bool
|
||||
fFunctions bool
|
||||
fTypes bool
|
||||
fVariables bool
|
||||
fDebug string
|
||||
fWholeProgram bool
|
||||
fReflection bool
|
||||
)
|
||||
|
||||
func newChecker(mode unused.CheckMode) *unused.Checker {
|
||||
checker := unused.NewChecker(mode)
|
||||
|
||||
if fDebug != "" {
|
||||
debug, err := os.Create(fDebug)
|
||||
if err != nil {
|
||||
log.Fatal("couldn't open debug file:", err)
|
||||
}
|
||||
checker.Debug = debug
|
||||
}
|
||||
|
||||
checker.WholeProgram = fWholeProgram
|
||||
checker.ConsiderReflection = fReflection
|
||||
return checker
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
|
||||
fs := lintutil.FlagSet("unused")
|
||||
fs.BoolVar(&fConstants, "consts", true, "Report unused constants")
|
||||
fs.BoolVar(&fFields, "fields", true, "Report unused fields")
|
||||
fs.BoolVar(&fFunctions, "funcs", true, "Report unused functions and methods")
|
||||
fs.BoolVar(&fTypes, "types", true, "Report unused types")
|
||||
fs.BoolVar(&fVariables, "vars", true, "Report unused variables")
|
||||
fs.StringVar(&fDebug, "debug", "", "Write a debug graph to `file`. Existing files will be overwritten.")
|
||||
fs.BoolVar(&fWholeProgram, "exported", false, "Treat arguments as a program and report unused exported identifiers")
|
||||
fs.BoolVar(&fReflection, "reflect", true, "Consider identifiers as used when it's likely they'll be accessed via reflection")
|
||||
fs.Parse(os.Args[1:])
|
||||
|
||||
var mode unused.CheckMode
|
||||
if fConstants {
|
||||
mode |= unused.CheckConstants
|
||||
}
|
||||
if fFields {
|
||||
mode |= unused.CheckFields
|
||||
}
|
||||
if fFunctions {
|
||||
mode |= unused.CheckFunctions
|
||||
}
|
||||
if fTypes {
|
||||
mode |= unused.CheckTypes
|
||||
}
|
||||
if fVariables {
|
||||
mode |= unused.CheckVariables
|
||||
}
|
||||
|
||||
checker := newChecker(mode)
|
||||
l := unused.NewLintChecker(checker)
|
||||
cfg := lintutil.CheckerConfig{
|
||||
Checker: l,
|
||||
ExitNonZero: true,
|
||||
}
|
||||
lintutil.ProcessFlagSet([]lintutil.CheckerConfig{cfg}, fs)
|
||||
}
|
162
vendor/honnef.co/go/tools/config/config.go
vendored
Normal file
162
vendor/honnef.co/go/tools/config/config.go
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
func mergeLists(a, b []string) []string {
|
||||
out := make([]string, 0, len(a)+len(b))
|
||||
for _, el := range b {
|
||||
if el == "inherit" {
|
||||
out = append(out, a...)
|
||||
} else {
|
||||
out = append(out, el)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func normalizeList(list []string) []string {
|
||||
if len(list) > 1 {
|
||||
nlist := make([]string, 0, len(list))
|
||||
nlist = append(nlist, list[0])
|
||||
for i, el := range list[1:] {
|
||||
if el != list[i] {
|
||||
nlist = append(nlist, el)
|
||||
}
|
||||
}
|
||||
list = nlist
|
||||
}
|
||||
|
||||
for _, el := range list {
|
||||
if el == "inherit" {
|
||||
// This should never happen, because the default config
|
||||
// should not use "inherit"
|
||||
panic(`unresolved "inherit"`)
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func (cfg Config) Merge(ocfg Config) Config {
|
||||
if ocfg.Checks != nil {
|
||||
cfg.Checks = mergeLists(cfg.Checks, ocfg.Checks)
|
||||
}
|
||||
if ocfg.Initialisms != nil {
|
||||
cfg.Initialisms = mergeLists(cfg.Initialisms, ocfg.Initialisms)
|
||||
}
|
||||
if ocfg.DotImportWhitelist != nil {
|
||||
cfg.DotImportWhitelist = mergeLists(cfg.DotImportWhitelist, ocfg.DotImportWhitelist)
|
||||
}
|
||||
if ocfg.HTTPStatusCodeWhitelist != nil {
|
||||
cfg.HTTPStatusCodeWhitelist = mergeLists(cfg.HTTPStatusCodeWhitelist, ocfg.HTTPStatusCodeWhitelist)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
// TODO(dh): this implementation makes it impossible for external
|
||||
// clients to add their own checkers with configuration. At the
|
||||
// moment, we don't really care about that; we don't encourage
|
||||
// that people use this package. In the future, we may. The
|
||||
// obvious solution would be using map[string]interface{}, but
|
||||
// that's obviously subpar.
|
||||
|
||||
Checks []string `toml:"checks"`
|
||||
Initialisms []string `toml:"initialisms"`
|
||||
DotImportWhitelist []string `toml:"dot_import_whitelist"`
|
||||
HTTPStatusCodeWhitelist []string `toml:"http_status_code_whitelist"`
|
||||
}
|
||||
|
||||
var defaultConfig = Config{
|
||||
Checks: []string{"all", "-ST1000", "-ST1003", "-ST1016"},
|
||||
Initialisms: []string{
|
||||
"ACL", "API", "ASCII", "CPU", "CSS", "DNS",
|
||||
"EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID",
|
||||
"IP", "JSON", "QPS", "RAM", "RPC", "SLA",
|
||||
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
|
||||
"UDP", "UI", "GID", "UID", "UUID", "URI",
|
||||
"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
|
||||
"XSS",
|
||||
},
|
||||
DotImportWhitelist: []string{},
|
||||
HTTPStatusCodeWhitelist: []string{"200", "400", "404", "500"},
|
||||
}
|
||||
|
||||
const configName = "staticcheck.conf"
|
||||
|
||||
func parseConfigs(dir string) ([]Config, error) {
|
||||
var out []Config
|
||||
|
||||
// TODO(dh): consider stopping at the GOPATH/module boundary
|
||||
for dir != "" {
|
||||
f, err := os.Open(filepath.Join(dir, configName))
|
||||
if os.IsNotExist(err) {
|
||||
ndir := filepath.Dir(dir)
|
||||
if ndir == dir {
|
||||
break
|
||||
}
|
||||
dir = ndir
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cfg Config
|
||||
_, err = toml.DecodeReader(f, &cfg)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, cfg)
|
||||
ndir := filepath.Dir(dir)
|
||||
if ndir == dir {
|
||||
break
|
||||
}
|
||||
dir = ndir
|
||||
}
|
||||
out = append(out, defaultConfig)
|
||||
if len(out) < 2 {
|
||||
return out, nil
|
||||
}
|
||||
for i := 0; i < len(out)/2; i++ {
|
||||
out[i], out[len(out)-1-i] = out[len(out)-1-i], out[i]
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func mergeConfigs(confs []Config) Config {
|
||||
if len(confs) == 0 {
|
||||
// This shouldn't happen because we always have at least a
|
||||
// default config.
|
||||
panic("trying to merge zero configs")
|
||||
}
|
||||
if len(confs) == 1 {
|
||||
return confs[0]
|
||||
}
|
||||
conf := confs[0]
|
||||
for _, oconf := range confs[1:] {
|
||||
conf = conf.Merge(oconf)
|
||||
}
|
||||
return conf
|
||||
}
|
||||
|
||||
func Load(dir string) (Config, error) {
|
||||
confs, err := parseConfigs(dir)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
conf := mergeConfigs(confs)
|
||||
|
||||
conf.Checks = normalizeList(conf.Checks)
|
||||
conf.Initialisms = normalizeList(conf.Initialisms)
|
||||
conf.DotImportWhitelist = normalizeList(conf.DotImportWhitelist)
|
||||
conf.HTTPStatusCodeWhitelist = normalizeList(conf.HTTPStatusCodeWhitelist)
|
||||
|
||||
return conf, nil
|
||||
}
|
10
vendor/honnef.co/go/tools/config/example.conf
vendored
Normal file
10
vendor/honnef.co/go/tools/config/example.conf
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
checks = ["all", "-ST1003", "-ST1014"]
|
||||
initialisms = ["ACL", "API", "ASCII", "CPU", "CSS", "DNS",
|
||||
"EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID",
|
||||
"IP", "JSON", "QPS", "RAM", "RPC", "SLA",
|
||||
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
|
||||
"UDP", "UI", "GID", "UID", "UUID", "URI",
|
||||
"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
|
||||
"XSS"]
|
||||
dot_import_whitelist = []
|
||||
http_status_code_whitelist = ["200", "400", "404", "500"]
|
38
vendor/honnef.co/go/tools/lint/generated.go
vendored
Normal file
38
vendor/honnef.co/go/tools/lint/generated.go
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
package lint
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
// used by cgo before Go 1.11
|
||||
oldCgo = []byte("// Created by cgo - DO NOT EDIT")
|
||||
prefix = []byte("// Code generated ")
|
||||
suffix = []byte(" DO NOT EDIT.")
|
||||
nl = []byte("\n")
|
||||
crnl = []byte("\r\n")
|
||||
)
|
||||
|
||||
func isGenerated(r io.Reader) bool {
|
||||
br := bufio.NewReader(r)
|
||||
for {
|
||||
s, err := br.ReadBytes('\n')
|
||||
if err != nil && err != io.EOF {
|
||||
return false
|
||||
}
|
||||
s = bytes.TrimSuffix(s, crnl)
|
||||
s = bytes.TrimSuffix(s, nl)
|
||||
if bytes.HasPrefix(s, prefix) && bytes.HasSuffix(s, suffix) {
|
||||
return true
|
||||
}
|
||||
if bytes.Equal(s, oldCgo) {
|
||||
return true
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
543
vendor/honnef.co/go/tools/lint/lint.go
vendored
543
vendor/honnef.co/go/tools/lint/lint.go
vendored
@ -1,25 +1,22 @@
|
||||
// Copyright (c) 2013 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd.
|
||||
|
||||
// Package lint provides the foundation for tools like gosimple.
|
||||
// Package lint provides the foundation for tools like staticcheck
|
||||
package lint // import "honnef.co/go/tools/lint"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"honnef.co/go/tools/config"
|
||||
"honnef.co/go/tools/ssa"
|
||||
"honnef.co/go/tools/ssa/ssautil"
|
||||
)
|
||||
@ -28,8 +25,10 @@ type Job struct {
|
||||
Program *Program
|
||||
|
||||
checker string
|
||||
check string
|
||||
check Check
|
||||
problems []Problem
|
||||
|
||||
duration time.Duration
|
||||
}
|
||||
|
||||
type Ignore interface {
|
||||
@ -89,7 +88,7 @@ type GlobIgnore struct {
|
||||
|
||||
func (gi *GlobIgnore) Match(p Problem) bool {
|
||||
if gi.Pattern != "*" {
|
||||
pkgpath := p.Package.Path()
|
||||
pkgpath := p.Package.Types.Path()
|
||||
if strings.HasSuffix(pkgpath, "_test") {
|
||||
pkgpath = pkgpath[:len(pkgpath)-len("_test")]
|
||||
}
|
||||
@ -107,31 +106,44 @@ func (gi *GlobIgnore) Match(p Problem) bool {
|
||||
}
|
||||
|
||||
type Program struct {
|
||||
SSA *ssa.Program
|
||||
Prog *loader.Program
|
||||
// TODO(dh): Rename to InitialPackages?
|
||||
Packages []*Pkg
|
||||
SSA *ssa.Program
|
||||
InitialPackages []*Pkg
|
||||
InitialFunctions []*ssa.Function
|
||||
AllPackages []*packages.Package
|
||||
AllFunctions []*ssa.Function
|
||||
Files []*ast.File
|
||||
Info *types.Info
|
||||
GoVersion int
|
||||
|
||||
tokenFileMap map[*token.File]*ast.File
|
||||
astFileMap map[*ast.File]*Pkg
|
||||
packagesMap map[string]*packages.Package
|
||||
|
||||
genMu sync.RWMutex
|
||||
generatedMap map[string]bool
|
||||
}
|
||||
|
||||
func (prog *Program) Fset() *token.FileSet {
|
||||
return prog.InitialPackages[0].Fset
|
||||
}
|
||||
|
||||
type Func func(*Job)
|
||||
|
||||
type Severity uint8
|
||||
|
||||
const (
|
||||
Error Severity = iota
|
||||
Warning
|
||||
Ignored
|
||||
)
|
||||
|
||||
// Problem represents a problem in some source code.
|
||||
type Problem struct {
|
||||
pos token.Pos
|
||||
Position token.Position // position in source file
|
||||
Text string // the prose that describes the problem
|
||||
Check string
|
||||
Checker string
|
||||
Package *types.Package
|
||||
Ignored bool
|
||||
Package *Pkg
|
||||
Severity Severity
|
||||
}
|
||||
|
||||
func (p *Problem) String() string {
|
||||
@ -145,15 +157,25 @@ type Checker interface {
|
||||
Name() string
|
||||
Prefix() string
|
||||
Init(*Program)
|
||||
Funcs() map[string]Func
|
||||
Checks() []Check
|
||||
}
|
||||
|
||||
type Check struct {
|
||||
Fn Func
|
||||
ID string
|
||||
FilterGenerated bool
|
||||
}
|
||||
|
||||
// A Linter lints Go source code.
|
||||
type Linter struct {
|
||||
Checker Checker
|
||||
Checkers []Checker
|
||||
Ignores []Ignore
|
||||
GoVersion int
|
||||
ReturnIgnored bool
|
||||
Config config.Config
|
||||
|
||||
MaxConcurrentJobs int
|
||||
PrintStats bool
|
||||
|
||||
automaticIgnores []Ignore
|
||||
}
|
||||
@ -191,36 +213,6 @@ func (j *Job) File(node Positioner) *ast.File {
|
||||
return j.Program.File(node)
|
||||
}
|
||||
|
||||
// TODO(dh): switch to sort.Slice when Go 1.9 lands.
|
||||
type byPosition struct {
|
||||
fset *token.FileSet
|
||||
ps []Problem
|
||||
}
|
||||
|
||||
func (ps byPosition) Len() int {
|
||||
return len(ps.ps)
|
||||
}
|
||||
|
||||
func (ps byPosition) Less(i int, j int) bool {
|
||||
pi, pj := ps.ps[i].Position, ps.ps[j].Position
|
||||
|
||||
if pi.Filename != pj.Filename {
|
||||
return pi.Filename < pj.Filename
|
||||
}
|
||||
if pi.Line != pj.Line {
|
||||
return pi.Line < pj.Line
|
||||
}
|
||||
if pi.Column != pj.Column {
|
||||
return pi.Column < pj.Column
|
||||
}
|
||||
|
||||
return ps.ps[i].Text < ps.ps[j].Text
|
||||
}
|
||||
|
||||
func (ps byPosition) Swap(i int, j int) {
|
||||
ps.ps[i], ps.ps[j] = ps.ps[j], ps.ps[i]
|
||||
}
|
||||
|
||||
func parseDirective(s string) (cmd string, args []string) {
|
||||
if !strings.HasPrefix(s, "//lint:") {
|
||||
return "", nil
|
||||
@ -230,79 +222,131 @@ func parseDirective(s string) (cmd string, args []string) {
|
||||
return fields[0], fields[1:]
|
||||
}
|
||||
|
||||
func (l *Linter) Lint(lprog *loader.Program, conf *loader.Config) []Problem {
|
||||
ssaprog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
|
||||
type PerfStats struct {
|
||||
PackageLoading time.Duration
|
||||
SSABuild time.Duration
|
||||
OtherInitWork time.Duration
|
||||
CheckerInits map[string]time.Duration
|
||||
Jobs []JobStat
|
||||
}
|
||||
|
||||
type JobStat struct {
|
||||
Job string
|
||||
Duration time.Duration
|
||||
}
|
||||
|
||||
func (stats *PerfStats) Print(w io.Writer) {
|
||||
fmt.Fprintln(w, "Package loading:", stats.PackageLoading)
|
||||
fmt.Fprintln(w, "SSA build:", stats.SSABuild)
|
||||
fmt.Fprintln(w, "Other init work:", stats.OtherInitWork)
|
||||
|
||||
fmt.Fprintln(w, "Checker inits:")
|
||||
for checker, d := range stats.CheckerInits {
|
||||
fmt.Fprintf(w, "\t%s: %s\n", checker, d)
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
|
||||
fmt.Fprintln(w, "Jobs:")
|
||||
sort.Slice(stats.Jobs, func(i, j int) bool {
|
||||
return stats.Jobs[i].Duration < stats.Jobs[j].Duration
|
||||
})
|
||||
var total time.Duration
|
||||
for _, job := range stats.Jobs {
|
||||
fmt.Fprintf(w, "\t%s: %s\n", job.Job, job.Duration)
|
||||
total += job.Duration
|
||||
}
|
||||
fmt.Fprintf(w, "\tTotal: %s\n", total)
|
||||
}
|
||||
|
||||
func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
|
||||
allPkgs := allPackages(initial)
|
||||
t := time.Now()
|
||||
ssaprog, _ := ssautil.Packages(allPkgs, ssa.GlobalDebug)
|
||||
ssaprog.Build()
|
||||
if stats != nil {
|
||||
stats.SSABuild = time.Since(t)
|
||||
}
|
||||
|
||||
t = time.Now()
|
||||
pkgMap := map[*ssa.Package]*Pkg{}
|
||||
var pkgs []*Pkg
|
||||
for _, pkginfo := range lprog.InitialPackages() {
|
||||
ssapkg := ssaprog.Package(pkginfo.Pkg)
|
||||
var bp *build.Package
|
||||
if len(pkginfo.Files) != 0 {
|
||||
path := lprog.Fset.Position(pkginfo.Files[0].Pos()).Filename
|
||||
for _, pkg := range initial {
|
||||
ssapkg := ssaprog.Package(pkg.Types)
|
||||
var cfg config.Config
|
||||
if len(pkg.GoFiles) != 0 {
|
||||
path := pkg.GoFiles[0]
|
||||
dir := filepath.Dir(path)
|
||||
var err error
|
||||
ctx := conf.Build
|
||||
if ctx == nil {
|
||||
ctx = &build.Default
|
||||
}
|
||||
bp, err = ctx.ImportDir(dir, 0)
|
||||
// OPT(dh): we're rebuilding the entire config tree for
|
||||
// each package. for example, if we check a/b/c and
|
||||
// a/b/c/d, we'll process a, a/b, a/b/c, a, a/b, a/b/c,
|
||||
// a/b/c/d – we should cache configs per package and only
|
||||
// load the new levels.
|
||||
cfg, err = config.Load(dir)
|
||||
if err != nil {
|
||||
// shouldn't happen
|
||||
// FIXME(dh): we couldn't load the config, what are we
|
||||
// supposed to do? probably tell the user somehow
|
||||
}
|
||||
cfg = cfg.Merge(l.Config)
|
||||
}
|
||||
|
||||
pkg := &Pkg{
|
||||
Package: ssapkg,
|
||||
Info: pkginfo,
|
||||
BuildPkg: bp,
|
||||
SSA: ssapkg,
|
||||
Package: pkg,
|
||||
Config: cfg,
|
||||
}
|
||||
pkgMap[ssapkg] = pkg
|
||||
pkgs = append(pkgs, pkg)
|
||||
}
|
||||
|
||||
prog := &Program{
|
||||
SSA: ssaprog,
|
||||
Prog: lprog,
|
||||
Packages: pkgs,
|
||||
Info: &types.Info{},
|
||||
GoVersion: l.GoVersion,
|
||||
tokenFileMap: map[*token.File]*ast.File{},
|
||||
astFileMap: map[*ast.File]*Pkg{},
|
||||
SSA: ssaprog,
|
||||
InitialPackages: pkgs,
|
||||
AllPackages: allPkgs,
|
||||
GoVersion: l.GoVersion,
|
||||
tokenFileMap: map[*token.File]*ast.File{},
|
||||
astFileMap: map[*ast.File]*Pkg{},
|
||||
generatedMap: map[string]bool{},
|
||||
}
|
||||
prog.packagesMap = map[string]*packages.Package{}
|
||||
for _, pkg := range allPkgs {
|
||||
prog.packagesMap[pkg.Types.Path()] = pkg
|
||||
}
|
||||
|
||||
initial := map[*types.Package]struct{}{}
|
||||
isInitial := map[*types.Package]struct{}{}
|
||||
for _, pkg := range pkgs {
|
||||
initial[pkg.Info.Pkg] = struct{}{}
|
||||
isInitial[pkg.Types] = struct{}{}
|
||||
}
|
||||
for fn := range ssautil.AllFunctions(ssaprog) {
|
||||
if fn.Pkg == nil {
|
||||
continue
|
||||
}
|
||||
prog.AllFunctions = append(prog.AllFunctions, fn)
|
||||
if _, ok := initial[fn.Pkg.Pkg]; ok {
|
||||
if _, ok := isInitial[fn.Pkg.Pkg]; ok {
|
||||
prog.InitialFunctions = append(prog.InitialFunctions, fn)
|
||||
}
|
||||
}
|
||||
for _, pkg := range pkgs {
|
||||
prog.Files = append(prog.Files, pkg.Info.Files...)
|
||||
prog.Files = append(prog.Files, pkg.Syntax...)
|
||||
|
||||
ssapkg := ssaprog.Package(pkg.Info.Pkg)
|
||||
for _, f := range pkg.Info.Files {
|
||||
ssapkg := ssaprog.Package(pkg.Types)
|
||||
for _, f := range pkg.Syntax {
|
||||
prog.astFileMap[f] = pkgMap[ssapkg]
|
||||
}
|
||||
}
|
||||
|
||||
for _, pkginfo := range lprog.AllPackages {
|
||||
for _, f := range pkginfo.Files {
|
||||
tf := lprog.Fset.File(f.Pos())
|
||||
for _, pkg := range allPkgs {
|
||||
for _, f := range pkg.Syntax {
|
||||
tf := pkg.Fset.File(f.Pos())
|
||||
prog.tokenFileMap[tf] = f
|
||||
}
|
||||
}
|
||||
|
||||
var out []Problem
|
||||
l.automaticIgnores = nil
|
||||
for _, pkginfo := range lprog.InitialPackages() {
|
||||
for _, f := range pkginfo.Files {
|
||||
cm := ast.NewCommentMap(lprog.Fset, f, f.Comments)
|
||||
for _, pkg := range initial {
|
||||
for _, f := range pkg.Syntax {
|
||||
cm := ast.NewCommentMap(pkg.Fset, f, f.Comments)
|
||||
for node, cgs := range cm {
|
||||
for _, cg := range cgs {
|
||||
for _, c := range cg.List {
|
||||
@ -315,11 +359,10 @@ func (l *Linter) Lint(lprog *loader.Program, conf *loader.Config) []Problem {
|
||||
if len(args) < 2 {
|
||||
// FIXME(dh): this causes duplicated warnings when using megacheck
|
||||
p := Problem{
|
||||
pos: c.Pos(),
|
||||
Position: prog.DisplayPosition(c.Pos()),
|
||||
Text: "malformed linter directive; missing the required reason field?",
|
||||
Check: "",
|
||||
Checker: l.Checker.Name(),
|
||||
Checker: "lint",
|
||||
Package: nil,
|
||||
}
|
||||
out = append(out, p)
|
||||
@ -362,75 +405,84 @@ func (l *Linter) Lint(lprog *loader.Program, conf *loader.Config) []Problem {
|
||||
scopes int
|
||||
}{}
|
||||
for _, pkg := range pkgs {
|
||||
sizes.types += len(pkg.Info.Info.Types)
|
||||
sizes.defs += len(pkg.Info.Info.Defs)
|
||||
sizes.uses += len(pkg.Info.Info.Uses)
|
||||
sizes.implicits += len(pkg.Info.Info.Implicits)
|
||||
sizes.selections += len(pkg.Info.Info.Selections)
|
||||
sizes.scopes += len(pkg.Info.Info.Scopes)
|
||||
sizes.types += len(pkg.TypesInfo.Types)
|
||||
sizes.defs += len(pkg.TypesInfo.Defs)
|
||||
sizes.uses += len(pkg.TypesInfo.Uses)
|
||||
sizes.implicits += len(pkg.TypesInfo.Implicits)
|
||||
sizes.selections += len(pkg.TypesInfo.Selections)
|
||||
sizes.scopes += len(pkg.TypesInfo.Scopes)
|
||||
}
|
||||
prog.Info.Types = make(map[ast.Expr]types.TypeAndValue, sizes.types)
|
||||
prog.Info.Defs = make(map[*ast.Ident]types.Object, sizes.defs)
|
||||
prog.Info.Uses = make(map[*ast.Ident]types.Object, sizes.uses)
|
||||
prog.Info.Implicits = make(map[ast.Node]types.Object, sizes.implicits)
|
||||
prog.Info.Selections = make(map[*ast.SelectorExpr]*types.Selection, sizes.selections)
|
||||
prog.Info.Scopes = make(map[ast.Node]*types.Scope, sizes.scopes)
|
||||
for _, pkg := range pkgs {
|
||||
for k, v := range pkg.Info.Info.Types {
|
||||
prog.Info.Types[k] = v
|
||||
}
|
||||
for k, v := range pkg.Info.Info.Defs {
|
||||
prog.Info.Defs[k] = v
|
||||
}
|
||||
for k, v := range pkg.Info.Info.Uses {
|
||||
prog.Info.Uses[k] = v
|
||||
}
|
||||
for k, v := range pkg.Info.Info.Implicits {
|
||||
prog.Info.Implicits[k] = v
|
||||
}
|
||||
for k, v := range pkg.Info.Info.Selections {
|
||||
prog.Info.Selections[k] = v
|
||||
}
|
||||
for k, v := range pkg.Info.Info.Scopes {
|
||||
prog.Info.Scopes[k] = v
|
||||
}
|
||||
}
|
||||
l.Checker.Init(prog)
|
||||
|
||||
funcs := l.Checker.Funcs()
|
||||
var keys []string
|
||||
for k := range funcs {
|
||||
keys = append(keys, k)
|
||||
if stats != nil {
|
||||
stats.OtherInitWork = time.Since(t)
|
||||
}
|
||||
|
||||
for _, checker := range l.Checkers {
|
||||
t := time.Now()
|
||||
checker.Init(prog)
|
||||
if stats != nil {
|
||||
stats.CheckerInits[checker.Name()] = time.Since(t)
|
||||
}
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var jobs []*Job
|
||||
for _, k := range keys {
|
||||
j := &Job{
|
||||
Program: prog,
|
||||
checker: l.Checker.Name(),
|
||||
check: k,
|
||||
var allChecks []string
|
||||
|
||||
for _, checker := range l.Checkers {
|
||||
checks := checker.Checks()
|
||||
for _, check := range checks {
|
||||
allChecks = append(allChecks, check.ID)
|
||||
j := &Job{
|
||||
Program: prog,
|
||||
checker: checker.Name(),
|
||||
check: check,
|
||||
}
|
||||
jobs = append(jobs, j)
|
||||
}
|
||||
jobs = append(jobs, j)
|
||||
}
|
||||
|
||||
max := len(jobs)
|
||||
if l.MaxConcurrentJobs > 0 {
|
||||
max = l.MaxConcurrentJobs
|
||||
}
|
||||
|
||||
sem := make(chan struct{}, max)
|
||||
wg := &sync.WaitGroup{}
|
||||
for _, j := range jobs {
|
||||
wg.Add(1)
|
||||
go func(j *Job) {
|
||||
defer wg.Done()
|
||||
fn := funcs[j.check]
|
||||
sem <- struct{}{}
|
||||
defer func() { <-sem }()
|
||||
fn := j.check.Fn
|
||||
if fn == nil {
|
||||
return
|
||||
}
|
||||
t := time.Now()
|
||||
fn(j)
|
||||
j.duration = time.Since(t)
|
||||
}(j)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
for _, j := range jobs {
|
||||
if stats != nil {
|
||||
stats.Jobs = append(stats.Jobs, JobStat{j.check.ID, j.duration})
|
||||
}
|
||||
for _, p := range j.problems {
|
||||
p.Ignored = l.ignore(p)
|
||||
if l.ReturnIgnored || !p.Ignored {
|
||||
allowedChecks := FilterChecks(allChecks, p.Package.Config.Checks)
|
||||
|
||||
if l.ignore(p) {
|
||||
p.Severity = Ignored
|
||||
}
|
||||
// TODO(dh): support globs in check white/blacklist
|
||||
// OPT(dh): this approach doesn't actually disable checks,
|
||||
// it just discards their results. For the moment, that's
|
||||
// fine. None of our checks are super expensive. In the
|
||||
// future, we may want to provide opt-in expensive
|
||||
// analysis, which shouldn't run at all. It may be easiest
|
||||
// to implement this in the individual checks.
|
||||
if (l.ReturnIgnored || p.Severity != Ignored) && allowedChecks[p.Check] {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
@ -444,39 +496,128 @@ func (l *Linter) Lint(lprog *loader.Program, conf *loader.Config) []Problem {
|
||||
if ig.matched {
|
||||
continue
|
||||
}
|
||||
for _, c := range ig.Checks {
|
||||
idx := strings.IndexFunc(c, func(r rune) bool {
|
||||
return unicode.IsNumber(r)
|
||||
})
|
||||
if idx == -1 {
|
||||
// malformed check name, backing out
|
||||
|
||||
couldveMatched := false
|
||||
for f, pkg := range prog.astFileMap {
|
||||
if prog.Fset().Position(f.Pos()).Filename != ig.File {
|
||||
continue
|
||||
}
|
||||
if c[:idx] != l.Checker.Prefix() {
|
||||
// not for this checker
|
||||
continue
|
||||
allowedChecks := FilterChecks(allChecks, pkg.Config.Checks)
|
||||
for _, c := range ig.Checks {
|
||||
if !allowedChecks[c] {
|
||||
continue
|
||||
}
|
||||
couldveMatched = true
|
||||
break
|
||||
}
|
||||
p := Problem{
|
||||
pos: ig.pos,
|
||||
Position: prog.DisplayPosition(ig.pos),
|
||||
Text: "this linter directive didn't match anything; should it be removed?",
|
||||
Check: "",
|
||||
Checker: l.Checker.Name(),
|
||||
Package: nil,
|
||||
}
|
||||
out = append(out, p)
|
||||
break
|
||||
}
|
||||
|
||||
if !couldveMatched {
|
||||
// The ignored checks were disabled for the containing package.
|
||||
// Don't flag the ignore for not having matched.
|
||||
continue
|
||||
}
|
||||
p := Problem{
|
||||
Position: prog.DisplayPosition(ig.pos),
|
||||
Text: "this linter directive didn't match anything; should it be removed?",
|
||||
Check: "",
|
||||
Checker: "lint",
|
||||
Package: nil,
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
|
||||
sort.Sort(byPosition{lprog.Fset, out})
|
||||
return out
|
||||
sort.Slice(out, func(i int, j int) bool {
|
||||
pi, pj := out[i].Position, out[j].Position
|
||||
|
||||
if pi.Filename != pj.Filename {
|
||||
return pi.Filename < pj.Filename
|
||||
}
|
||||
if pi.Line != pj.Line {
|
||||
return pi.Line < pj.Line
|
||||
}
|
||||
if pi.Column != pj.Column {
|
||||
return pi.Column < pj.Column
|
||||
}
|
||||
|
||||
return out[i].Text < out[j].Text
|
||||
})
|
||||
|
||||
if l.PrintStats && stats != nil {
|
||||
stats.Print(os.Stderr)
|
||||
}
|
||||
|
||||
if len(out) < 2 {
|
||||
return out
|
||||
}
|
||||
|
||||
uniq := make([]Problem, 0, len(out))
|
||||
uniq = append(uniq, out[0])
|
||||
prev := out[0]
|
||||
for _, p := range out[1:] {
|
||||
if prev.Position == p.Position && prev.Text == p.Text {
|
||||
continue
|
||||
}
|
||||
prev = p
|
||||
uniq = append(uniq, p)
|
||||
}
|
||||
|
||||
return uniq
|
||||
}
|
||||
|
||||
func FilterChecks(allChecks []string, checks []string) map[string]bool {
|
||||
// OPT(dh): this entire computation could be cached per package
|
||||
allowedChecks := map[string]bool{}
|
||||
|
||||
for _, check := range checks {
|
||||
b := true
|
||||
if len(check) > 1 && check[0] == '-' {
|
||||
b = false
|
||||
check = check[1:]
|
||||
}
|
||||
if check == "*" || check == "all" {
|
||||
// Match all
|
||||
for _, c := range allChecks {
|
||||
allowedChecks[c] = b
|
||||
}
|
||||
} else if strings.HasSuffix(check, "*") {
|
||||
// Glob
|
||||
prefix := check[:len(check)-1]
|
||||
isCat := strings.IndexFunc(prefix, func(r rune) bool { return unicode.IsNumber(r) }) == -1
|
||||
|
||||
for _, c := range allChecks {
|
||||
idx := strings.IndexFunc(c, func(r rune) bool { return unicode.IsNumber(r) })
|
||||
if isCat {
|
||||
// Glob is S*, which should match S1000 but not SA1000
|
||||
cat := c[:idx]
|
||||
if prefix == cat {
|
||||
allowedChecks[c] = b
|
||||
}
|
||||
} else {
|
||||
// Glob is S1*
|
||||
if strings.HasPrefix(c, prefix) {
|
||||
allowedChecks[c] = b
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Literal check name
|
||||
allowedChecks[check] = b
|
||||
}
|
||||
}
|
||||
return allowedChecks
|
||||
}
|
||||
|
||||
func (prog *Program) Package(path string) *packages.Package {
|
||||
return prog.packagesMap[path]
|
||||
}
|
||||
|
||||
// Pkg represents a package being linted.
|
||||
type Pkg struct {
|
||||
*ssa.Package
|
||||
Info *loader.PackageInfo
|
||||
BuildPkg *build.Package
|
||||
SSA *ssa.Package
|
||||
*packages.Package
|
||||
Config config.Config
|
||||
}
|
||||
|
||||
type Positioner interface {
|
||||
@ -484,52 +625,61 @@ type Positioner interface {
|
||||
}
|
||||
|
||||
func (prog *Program) DisplayPosition(p token.Pos) token.Position {
|
||||
// The //line compiler directive can be used to change the file
|
||||
// name and line numbers associated with code. This can, for
|
||||
// example, be used by code generation tools. The most prominent
|
||||
// example is 'go tool cgo', which uses //line directives to refer
|
||||
// back to the original source code.
|
||||
//
|
||||
// In the context of our linters, we need to treat these
|
||||
// directives differently depending on context. For cgo files, we
|
||||
// want to honour the directives, so that line numbers are
|
||||
// adjusted correctly. For all other files, we want to ignore the
|
||||
// directives, so that problems are reported at their actual
|
||||
// position and not, for example, a yacc grammar file. This also
|
||||
// affects the ignore mechanism, since it operates on the position
|
||||
// information stored within problems. With this implementation, a
|
||||
// user will ignore foo.go, not foo.y
|
||||
// Only use the adjusted position if it points to another Go file.
|
||||
// This means we'll point to the original file for cgo files, but
|
||||
// we won't point to a YACC grammar file.
|
||||
|
||||
pkg := prog.astFileMap[prog.tokenFileMap[prog.Prog.Fset.File(p)]]
|
||||
bp := pkg.BuildPkg
|
||||
adjPos := prog.Prog.Fset.Position(p)
|
||||
if bp == nil {
|
||||
// couldn't find the package for some reason (deleted? faulty
|
||||
// file system?)
|
||||
pos := prog.Fset().PositionFor(p, false)
|
||||
adjPos := prog.Fset().PositionFor(p, true)
|
||||
|
||||
if filepath.Ext(adjPos.Filename) == ".go" {
|
||||
return adjPos
|
||||
}
|
||||
base := filepath.Base(adjPos.Filename)
|
||||
for _, f := range bp.CgoFiles {
|
||||
if f == base {
|
||||
// this is a cgo file, use the adjusted position
|
||||
return adjPos
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
func (prog *Program) isGenerated(path string) bool {
|
||||
// This function isn't very efficient in terms of lock contention
|
||||
// and lack of parallelism, but it really shouldn't matter.
|
||||
// Projects consists of thousands of files, and have hundreds of
|
||||
// errors. That's not a lot of calls to isGenerated.
|
||||
|
||||
prog.genMu.RLock()
|
||||
if b, ok := prog.generatedMap[path]; ok {
|
||||
prog.genMu.RUnlock()
|
||||
return b
|
||||
}
|
||||
// not a cgo file, ignore //line directives
|
||||
return prog.Prog.Fset.PositionFor(p, false)
|
||||
prog.genMu.RUnlock()
|
||||
prog.genMu.Lock()
|
||||
defer prog.genMu.Unlock()
|
||||
// recheck to avoid doing extra work in case of race
|
||||
if b, ok := prog.generatedMap[path]; ok {
|
||||
return b
|
||||
}
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer f.Close()
|
||||
b := isGenerated(f)
|
||||
prog.generatedMap[path] = b
|
||||
return b
|
||||
}
|
||||
|
||||
func (j *Job) Errorf(n Positioner, format string, args ...interface{}) *Problem {
|
||||
tf := j.Program.SSA.Fset.File(n.Pos())
|
||||
f := j.Program.tokenFileMap[tf]
|
||||
pkg := j.Program.astFileMap[f].Pkg
|
||||
pkg := j.Program.astFileMap[f]
|
||||
|
||||
pos := j.Program.DisplayPosition(n.Pos())
|
||||
if j.Program.isGenerated(pos.Filename) && j.check.FilterGenerated {
|
||||
return nil
|
||||
}
|
||||
problem := Problem{
|
||||
pos: n.Pos(),
|
||||
Position: pos,
|
||||
Text: fmt.Sprintf(format, args...),
|
||||
Check: j.check,
|
||||
Check: j.check.ID,
|
||||
Checker: j.checker,
|
||||
Package: pkg,
|
||||
}
|
||||
@ -541,3 +691,16 @@ func (j *Job) NodePackage(node Positioner) *Pkg {
|
||||
f := j.File(node)
|
||||
return j.Program.astFileMap[f]
|
||||
}
|
||||
|
||||
func allPackages(pkgs []*packages.Package) []*packages.Package {
|
||||
var out []*packages.Package
|
||||
packages.Visit(
|
||||
pkgs,
|
||||
func(pkg *packages.Package) bool {
|
||||
out = append(out, pkg)
|
||||
return true
|
||||
},
|
||||
nil,
|
||||
)
|
||||
return out
|
||||
}
|
||||
|
129
vendor/honnef.co/go/tools/lint/lintdsl/lintdsl.go
vendored
129
vendor/honnef.co/go/tools/lint/lintdsl/lintdsl.go
vendored
@ -103,10 +103,21 @@ func IsZero(expr ast.Expr) bool {
|
||||
return IsIntLiteral(expr, "0")
|
||||
}
|
||||
|
||||
func TypeOf(j *lint.Job, expr ast.Expr) types.Type { return j.Program.Info.TypeOf(expr) }
|
||||
func TypeOf(j *lint.Job, expr ast.Expr) types.Type {
|
||||
if expr == nil {
|
||||
return nil
|
||||
}
|
||||
return j.NodePackage(expr).TypesInfo.TypeOf(expr)
|
||||
}
|
||||
|
||||
func IsOfType(j *lint.Job, expr ast.Expr, name string) bool { return IsType(TypeOf(j, expr), name) }
|
||||
|
||||
func ObjectOf(j *lint.Job, ident *ast.Ident) types.Object { return j.Program.Info.ObjectOf(ident) }
|
||||
func ObjectOf(j *lint.Job, ident *ast.Ident) types.Object {
|
||||
if ident == nil {
|
||||
return nil
|
||||
}
|
||||
return j.NodePackage(ident).TypesInfo.ObjectOf(ident)
|
||||
}
|
||||
|
||||
func IsInTest(j *lint.Job, node lint.Positioner) bool {
|
||||
// FIXME(dh): this doesn't work for global variables with
|
||||
@ -123,14 +134,15 @@ func IsInMain(j *lint.Job, node lint.Positioner) bool {
|
||||
if pkg == nil {
|
||||
return false
|
||||
}
|
||||
return pkg.Pkg.Name() == "main"
|
||||
return pkg.Types.Name() == "main"
|
||||
}
|
||||
|
||||
func SelectorName(j *lint.Job, expr *ast.SelectorExpr) string {
|
||||
sel := j.Program.Info.Selections[expr]
|
||||
info := j.NodePackage(expr).TypesInfo
|
||||
sel := info.Selections[expr]
|
||||
if sel == nil {
|
||||
if x, ok := expr.X.(*ast.Ident); ok {
|
||||
pkg, ok := j.Program.Info.ObjectOf(x).(*types.PkgName)
|
||||
pkg, ok := info.ObjectOf(x).(*types.PkgName)
|
||||
if !ok {
|
||||
// This shouldn't happen
|
||||
return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name)
|
||||
@ -143,11 +155,11 @@ func SelectorName(j *lint.Job, expr *ast.SelectorExpr) string {
|
||||
}
|
||||
|
||||
func IsNil(j *lint.Job, expr ast.Expr) bool {
|
||||
return j.Program.Info.Types[expr].IsNil()
|
||||
return j.NodePackage(expr).TypesInfo.Types[expr].IsNil()
|
||||
}
|
||||
|
||||
func BoolConst(j *lint.Job, expr ast.Expr) bool {
|
||||
val := j.Program.Info.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
|
||||
val := j.NodePackage(expr).TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
|
||||
return constant.BoolVal(val)
|
||||
}
|
||||
|
||||
@ -160,7 +172,7 @@ func IsBoolConst(j *lint.Job, expr ast.Expr) bool {
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
obj := j.Program.Info.ObjectOf(ident)
|
||||
obj := j.NodePackage(expr).TypesInfo.ObjectOf(ident)
|
||||
c, ok := obj.(*types.Const)
|
||||
if !ok {
|
||||
return false
|
||||
@ -176,7 +188,7 @@ func IsBoolConst(j *lint.Job, expr ast.Expr) bool {
|
||||
}
|
||||
|
||||
func ExprToInt(j *lint.Job, expr ast.Expr) (int64, bool) {
|
||||
tv := j.Program.Info.Types[expr]
|
||||
tv := j.NodePackage(expr).TypesInfo.Types[expr]
|
||||
if tv.Value == nil {
|
||||
return 0, false
|
||||
}
|
||||
@ -187,7 +199,7 @@ func ExprToInt(j *lint.Job, expr ast.Expr) (int64, bool) {
|
||||
}
|
||||
|
||||
func ExprToString(j *lint.Job, expr ast.Expr) (string, bool) {
|
||||
val := j.Program.Info.Types[expr].Value
|
||||
val := j.NodePackage(expr).TypesInfo.Types[expr].Value
|
||||
if val == nil {
|
||||
return "", false
|
||||
}
|
||||
@ -220,17 +232,35 @@ func IsGoVersion(j *lint.Job, minor int) bool {
|
||||
return j.Program.GoVersion >= minor
|
||||
}
|
||||
|
||||
func CallNameAST(j *lint.Job, call *ast.CallExpr) string {
|
||||
switch fun := call.Fun.(type) {
|
||||
case *ast.SelectorExpr:
|
||||
fn, ok := ObjectOf(j, fun.Sel).(*types.Func)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fn.FullName()
|
||||
case *ast.Ident:
|
||||
obj := ObjectOf(j, fun)
|
||||
switch obj := obj.(type) {
|
||||
case *types.Func:
|
||||
return obj.FullName()
|
||||
case *types.Builtin:
|
||||
return obj.Name()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func IsCallToAST(j *lint.Job, node ast.Node, name string) bool {
|
||||
call, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
sel, ok := call.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
fn, ok := j.Program.Info.ObjectOf(sel.Sel).(*types.Func)
|
||||
return ok && fn.FullName() == name
|
||||
return CallNameAST(j, call) == name
|
||||
}
|
||||
|
||||
func IsCallToAnyAST(j *lint.Job, node ast.Node, names ...string) bool {
|
||||
@ -280,3 +310,70 @@ func Inspect(node ast.Node, fn func(node ast.Node) bool) {
|
||||
}
|
||||
ast.Inspect(node, fn)
|
||||
}
|
||||
|
||||
func GroupSpecs(j *lint.Job, specs []ast.Spec) [][]ast.Spec {
|
||||
if len(specs) == 0 {
|
||||
return nil
|
||||
}
|
||||
fset := j.Program.SSA.Fset
|
||||
groups := make([][]ast.Spec, 1)
|
||||
groups[0] = append(groups[0], specs[0])
|
||||
|
||||
for _, spec := range specs[1:] {
|
||||
g := groups[len(groups)-1]
|
||||
if fset.PositionFor(spec.Pos(), false).Line-1 !=
|
||||
fset.PositionFor(g[len(g)-1].End(), false).Line {
|
||||
|
||||
groups = append(groups, nil)
|
||||
}
|
||||
|
||||
groups[len(groups)-1] = append(groups[len(groups)-1], spec)
|
||||
}
|
||||
|
||||
return groups
|
||||
}
|
||||
|
||||
func IsObject(obj types.Object, name string) bool {
|
||||
var path string
|
||||
if pkg := obj.Pkg(); pkg != nil {
|
||||
path = pkg.Path() + "."
|
||||
}
|
||||
return path+obj.Name() == name
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
Var *types.Var
|
||||
Tag string
|
||||
Path []int
|
||||
}
|
||||
|
||||
// FlattenFields recursively flattens T and embedded structs,
|
||||
// returning a list of fields. If multiple fields with the same name
|
||||
// exist, all will be returned.
|
||||
func FlattenFields(T *types.Struct) []Field {
|
||||
return flattenFields(T, nil, nil)
|
||||
}
|
||||
|
||||
func flattenFields(T *types.Struct, path []int, seen map[types.Type]bool) []Field {
|
||||
if seen == nil {
|
||||
seen = map[types.Type]bool{}
|
||||
}
|
||||
if seen[T] {
|
||||
return nil
|
||||
}
|
||||
seen[T] = true
|
||||
var out []Field
|
||||
for i := 0; i < T.NumFields(); i++ {
|
||||
field := T.Field(i)
|
||||
tag := T.Tag(i)
|
||||
np := append(path[:len(path):len(path)], i)
|
||||
if field.Anonymous() {
|
||||
if s, ok := Dereference(field.Type()).Underlying().(*types.Struct); ok {
|
||||
out = append(out, flattenFields(s, np, seen)...)
|
||||
}
|
||||
} else {
|
||||
out = append(out, Field{field, tag, np})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
128
vendor/honnef.co/go/tools/lint/lintutil/format/format.go
vendored
Normal file
128
vendor/honnef.co/go/tools/lint/lintutil/format/format.go
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
// Package format provides formatters for linter problems.
|
||||
package format
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/tabwriter"
|
||||
|
||||
"honnef.co/go/tools/lint"
|
||||
)
|
||||
|
||||
func shortPath(path string) string {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return path
|
||||
}
|
||||
if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) {
|
||||
return rel
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func relativePositionString(pos token.Position) string {
|
||||
s := shortPath(pos.Filename)
|
||||
if pos.IsValid() {
|
||||
if s != "" {
|
||||
s += ":"
|
||||
}
|
||||
s += fmt.Sprintf("%d:%d", pos.Line, pos.Column)
|
||||
}
|
||||
if s == "" {
|
||||
s = "-"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type Statter interface {
|
||||
Stats(total, errors, warnings int)
|
||||
}
|
||||
|
||||
type Formatter interface {
|
||||
Format(p lint.Problem)
|
||||
}
|
||||
|
||||
type Text struct {
|
||||
W io.Writer
|
||||
}
|
||||
|
||||
func (o Text) Format(p lint.Problem) {
|
||||
fmt.Fprintf(o.W, "%v: %s\n", relativePositionString(p.Position), p.String())
|
||||
}
|
||||
|
||||
type JSON struct {
|
||||
W io.Writer
|
||||
}
|
||||
|
||||
func severity(s lint.Severity) string {
|
||||
switch s {
|
||||
case lint.Error:
|
||||
return "error"
|
||||
case lint.Warning:
|
||||
return "warning"
|
||||
case lint.Ignored:
|
||||
return "ignored"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (o JSON) Format(p lint.Problem) {
|
||||
type location struct {
|
||||
File string `json:"file"`
|
||||
Line int `json:"line"`
|
||||
Column int `json:"column"`
|
||||
}
|
||||
jp := struct {
|
||||
Code string `json:"code"`
|
||||
Severity string `json:"severity,omitempty"`
|
||||
Location location `json:"location"`
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Code: p.Check,
|
||||
Severity: severity(p.Severity),
|
||||
Location: location{
|
||||
File: p.Position.Filename,
|
||||
Line: p.Position.Line,
|
||||
Column: p.Position.Column,
|
||||
},
|
||||
Message: p.Text,
|
||||
}
|
||||
_ = json.NewEncoder(o.W).Encode(jp)
|
||||
}
|
||||
|
||||
type Stylish struct {
|
||||
W io.Writer
|
||||
|
||||
prevFile string
|
||||
tw *tabwriter.Writer
|
||||
}
|
||||
|
||||
func (o *Stylish) Format(p lint.Problem) {
|
||||
if p.Position.Filename == "" {
|
||||
p.Position.Filename = "-"
|
||||
}
|
||||
|
||||
if p.Position.Filename != o.prevFile {
|
||||
if o.prevFile != "" {
|
||||
o.tw.Flush()
|
||||
fmt.Fprintln(o.W)
|
||||
}
|
||||
fmt.Fprintln(o.W, p.Position.Filename)
|
||||
o.prevFile = p.Position.Filename
|
||||
o.tw = tabwriter.NewWriter(o.W, 0, 4, 2, ' ', 0)
|
||||
}
|
||||
fmt.Fprintf(o.tw, " (%d, %d)\t%s\t%s\n", p.Position.Line, p.Position.Column, p.Check, p.Text)
|
||||
}
|
||||
|
||||
func (o *Stylish) Stats(total, errors, warnings int) {
|
||||
if o.tw != nil {
|
||||
o.tw.Flush()
|
||||
fmt.Fprintln(o.W)
|
||||
}
|
||||
fmt.Fprintf(o.W, " ✖ %d problems (%d errors, %d warnings)\n",
|
||||
total, errors, warnings)
|
||||
}
|
374
vendor/honnef.co/go/tools/lint/lintutil/util.go
vendored
374
vendor/honnef.co/go/tools/lint/lintutil/util.go
vendored
@ -8,70 +8,28 @@
|
||||
package lintutil // import "honnef.co/go/tools/lint/lintutil"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"honnef.co/go/tools/config"
|
||||
"honnef.co/go/tools/lint"
|
||||
"honnef.co/go/tools/lint/lintutil/format"
|
||||
"honnef.co/go/tools/version"
|
||||
|
||||
"github.com/kisielk/gotool"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
type OutputFormatter interface {
|
||||
Format(p lint.Problem)
|
||||
}
|
||||
|
||||
type TextOutput struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (o TextOutput) Format(p lint.Problem) {
|
||||
fmt.Fprintf(o.w, "%v: %s\n", relativePositionString(p.Position), p.String())
|
||||
}
|
||||
|
||||
type JSONOutput struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (o JSONOutput) Format(p lint.Problem) {
|
||||
type location struct {
|
||||
File string `json:"file"`
|
||||
Line int `json:"line"`
|
||||
Column int `json:"column"`
|
||||
}
|
||||
jp := struct {
|
||||
Checker string `json:"checker"`
|
||||
Code string `json:"code"`
|
||||
Severity string `json:"severity,omitempty"`
|
||||
Location location `json:"location"`
|
||||
Message string `json:"message"`
|
||||
Ignored bool `json:"ignored"`
|
||||
}{
|
||||
p.Checker,
|
||||
p.Check,
|
||||
"", // TODO(dh): support severity
|
||||
location{
|
||||
p.Position.Filename,
|
||||
p.Position.Line,
|
||||
p.Position.Column,
|
||||
},
|
||||
p.Text,
|
||||
p.Ignored,
|
||||
}
|
||||
_ = json.NewEncoder(o.w).Encode(jp)
|
||||
}
|
||||
func usage(name string, flags *flag.FlagSet) func() {
|
||||
return func() {
|
||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n", name)
|
||||
@ -84,38 +42,6 @@ func usage(name string, flags *flag.FlagSet) func() {
|
||||
}
|
||||
}
|
||||
|
||||
type runner struct {
|
||||
checker lint.Checker
|
||||
tags []string
|
||||
ignores []lint.Ignore
|
||||
version int
|
||||
returnIgnored bool
|
||||
}
|
||||
|
||||
func resolveRelative(importPaths []string, tags []string) (goFiles bool, err error) {
|
||||
if len(importPaths) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
if strings.HasSuffix(importPaths[0], ".go") {
|
||||
// User is specifying a package in terms of .go files, don't resolve
|
||||
return true, nil
|
||||
}
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
ctx := build.Default
|
||||
ctx.BuildTags = tags
|
||||
for i, path := range importPaths {
|
||||
bpkg, err := ctx.Import(path, wd, build.FindOnly)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("can't load package %q: %v", path, err)
|
||||
}
|
||||
importPaths[i] = bpkg.ImportPath
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func parseIgnore(s string) ([]lint.Ignore, error) {
|
||||
var out []lint.Ignore
|
||||
if len(s) == 0 {
|
||||
@ -158,16 +84,41 @@ func (v *versionFlag) Get() interface{} {
|
||||
return int(*v)
|
||||
}
|
||||
|
||||
type list []string
|
||||
|
||||
func (list *list) String() string {
|
||||
return `"` + strings.Join(*list, ",") + `"`
|
||||
}
|
||||
|
||||
func (list *list) Set(s string) error {
|
||||
if s == "" {
|
||||
*list = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
*list = strings.Split(s, ",")
|
||||
return nil
|
||||
}
|
||||
|
||||
func FlagSet(name string) *flag.FlagSet {
|
||||
flags := flag.NewFlagSet("", flag.ExitOnError)
|
||||
flags.Usage = usage(name, flags)
|
||||
flags.Float64("min_confidence", 0, "Deprecated; use -ignore instead")
|
||||
flags.String("tags", "", "List of `build tags`")
|
||||
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'")
|
||||
flags.String("ignore", "", "Deprecated: use linter directives instead")
|
||||
flags.Bool("tests", true, "Include tests")
|
||||
flags.Bool("version", false, "Print version and exit")
|
||||
flags.Bool("show-ignored", false, "Don't filter ignored problems")
|
||||
flags.String("f", "text", "Output `format` (valid choices are 'text' and 'json')")
|
||||
flags.String("f", "text", "Output `format` (valid choices are 'stylish', 'text' and 'json')")
|
||||
|
||||
flags.Int("debug.max-concurrent-jobs", 0, "Number of jobs to run concurrently")
|
||||
flags.Bool("debug.print-stats", false, "Print debug statistics")
|
||||
flags.String("debug.cpuprofile", "", "Write CPU profile to `file`")
|
||||
flags.String("debug.memprofile", "", "Write memory profile to `file`")
|
||||
|
||||
checks := list{"inherit"}
|
||||
fail := list{"all"}
|
||||
flags.Var(&checks, "checks", "Comma-separated list of `checks` to enable.")
|
||||
flags.Var(&fail, "fail", "Comma-separated list of `checks` that can cause a non-zero exit status.")
|
||||
|
||||
tags := build.Default.ReleaseTags
|
||||
v := tags[len(tags)-1][2:]
|
||||
@ -180,76 +131,129 @@ func FlagSet(name string) *flag.FlagSet {
|
||||
return flags
|
||||
}
|
||||
|
||||
type CheckerConfig struct {
|
||||
Checker lint.Checker
|
||||
ExitNonZero bool
|
||||
}
|
||||
|
||||
func ProcessFlagSet(confs []CheckerConfig, fs *flag.FlagSet) {
|
||||
func ProcessFlagSet(cs []lint.Checker, fs *flag.FlagSet) {
|
||||
tags := fs.Lookup("tags").Value.(flag.Getter).Get().(string)
|
||||
ignore := fs.Lookup("ignore").Value.(flag.Getter).Get().(string)
|
||||
tests := fs.Lookup("tests").Value.(flag.Getter).Get().(bool)
|
||||
goVersion := fs.Lookup("go").Value.(flag.Getter).Get().(int)
|
||||
format := fs.Lookup("f").Value.(flag.Getter).Get().(string)
|
||||
formatter := fs.Lookup("f").Value.(flag.Getter).Get().(string)
|
||||
printVersion := fs.Lookup("version").Value.(flag.Getter).Get().(bool)
|
||||
showIgnored := fs.Lookup("show-ignored").Value.(flag.Getter).Get().(bool)
|
||||
|
||||
if printVersion {
|
||||
version.Print()
|
||||
os.Exit(0)
|
||||
maxConcurrentJobs := fs.Lookup("debug.max-concurrent-jobs").Value.(flag.Getter).Get().(int)
|
||||
printStats := fs.Lookup("debug.print-stats").Value.(flag.Getter).Get().(bool)
|
||||
cpuProfile := fs.Lookup("debug.cpuprofile").Value.(flag.Getter).Get().(string)
|
||||
memProfile := fs.Lookup("debug.memprofile").Value.(flag.Getter).Get().(string)
|
||||
|
||||
cfg := config.Config{}
|
||||
cfg.Checks = *fs.Lookup("checks").Value.(*list)
|
||||
|
||||
exit := func(code int) {
|
||||
if cpuProfile != "" {
|
||||
pprof.StopCPUProfile()
|
||||
}
|
||||
if memProfile != "" {
|
||||
f, err := os.Create(memProfile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
runtime.GC()
|
||||
pprof.WriteHeapProfile(f)
|
||||
}
|
||||
os.Exit(code)
|
||||
}
|
||||
if cpuProfile != "" {
|
||||
f, err := os.Create(cpuProfile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
pprof.StartCPUProfile(f)
|
||||
}
|
||||
|
||||
var cs []lint.Checker
|
||||
for _, conf := range confs {
|
||||
cs = append(cs, conf.Checker)
|
||||
if printVersion {
|
||||
version.Print()
|
||||
exit(0)
|
||||
}
|
||||
pss, err := Lint(cs, fs.Args(), &Options{
|
||||
|
||||
ps, err := Lint(cs, fs.Args(), &Options{
|
||||
Tags: strings.Fields(tags),
|
||||
LintTests: tests,
|
||||
Ignores: ignore,
|
||||
GoVersion: goVersion,
|
||||
ReturnIgnored: showIgnored,
|
||||
Config: cfg,
|
||||
|
||||
MaxConcurrentJobs: maxConcurrentJobs,
|
||||
PrintStats: printStats,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
var ps []lint.Problem
|
||||
for _, p := range pss {
|
||||
ps = append(ps, p...)
|
||||
}
|
||||
|
||||
var f OutputFormatter
|
||||
switch format {
|
||||
var f format.Formatter
|
||||
switch formatter {
|
||||
case "text":
|
||||
f = TextOutput{os.Stdout}
|
||||
f = format.Text{W: os.Stdout}
|
||||
case "stylish":
|
||||
f = &format.Stylish{W: os.Stdout}
|
||||
case "json":
|
||||
f = JSONOutput{os.Stdout}
|
||||
f = format.JSON{W: os.Stdout}
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unsupported output format %q\n", format)
|
||||
os.Exit(2)
|
||||
fmt.Fprintf(os.Stderr, "unsupported output format %q\n", formatter)
|
||||
exit(2)
|
||||
}
|
||||
|
||||
var (
|
||||
total int
|
||||
errors int
|
||||
warnings int
|
||||
)
|
||||
|
||||
fail := *fs.Lookup("fail").Value.(*list)
|
||||
var allChecks []string
|
||||
for _, p := range ps {
|
||||
allChecks = append(allChecks, p.Check)
|
||||
}
|
||||
|
||||
shouldExit := lint.FilterChecks(allChecks, fail)
|
||||
|
||||
total = len(ps)
|
||||
for _, p := range ps {
|
||||
if shouldExit[p.Check] {
|
||||
errors++
|
||||
} else {
|
||||
p.Severity = lint.Warning
|
||||
warnings++
|
||||
}
|
||||
f.Format(p)
|
||||
}
|
||||
for i, p := range pss {
|
||||
if len(p) != 0 && confs[i].ExitNonZero {
|
||||
os.Exit(1)
|
||||
}
|
||||
if f, ok := f.(format.Statter); ok {
|
||||
f.Stats(total, errors, warnings)
|
||||
}
|
||||
if errors > 0 {
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Config config.Config
|
||||
|
||||
Tags []string
|
||||
LintTests bool
|
||||
Ignores string
|
||||
GoVersion int
|
||||
ReturnIgnored bool
|
||||
|
||||
MaxConcurrentJobs int
|
||||
PrintStats bool
|
||||
}
|
||||
|
||||
func Lint(cs []lint.Checker, pkgs []string, opt *Options) ([][]lint.Problem, error) {
|
||||
func Lint(cs []lint.Checker, paths []string, opt *Options) ([]lint.Problem, error) {
|
||||
stats := lint.PerfStats{
|
||||
CheckerInits: map[string]time.Duration{},
|
||||
}
|
||||
|
||||
if opt == nil {
|
||||
opt = &Options{}
|
||||
}
|
||||
@ -257,94 +261,102 @@ func Lint(cs []lint.Checker, pkgs []string, opt *Options) ([][]lint.Problem, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paths := gotool.ImportPaths(pkgs)
|
||||
goFiles, err := resolveRelative(paths, opt.Tags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx := build.Default
|
||||
ctx.BuildTags = opt.Tags
|
||||
hadError := false
|
||||
conf := &loader.Config{
|
||||
Build: &ctx,
|
||||
ParserMode: parser.ParseComments,
|
||||
ImportPkgs: map[string]bool{},
|
||||
TypeChecker: types.Config{
|
||||
Sizes: types.SizesFor(ctx.Compiler, ctx.GOARCH),
|
||||
Error: func(err error) {
|
||||
// Only print the first error found
|
||||
if hadError {
|
||||
return
|
||||
}
|
||||
hadError = true
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
},
|
||||
|
||||
conf := &packages.Config{
|
||||
Mode: packages.LoadAllSyntax,
|
||||
Tests: opt.LintTests,
|
||||
BuildFlags: []string{
|
||||
"-tags=" + strings.Join(opt.Tags, " "),
|
||||
},
|
||||
}
|
||||
if goFiles {
|
||||
conf.CreateFromFilenames("adhoc", paths...)
|
||||
} else {
|
||||
for _, path := range paths {
|
||||
conf.ImportPkgs[path] = opt.LintTests
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
if len(paths) == 0 {
|
||||
paths = []string{"."}
|
||||
}
|
||||
lprog, err := conf.Load()
|
||||
pkgs, err := packages.Load(conf, paths...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.PackageLoading = time.Since(t)
|
||||
|
||||
var problems [][]lint.Problem
|
||||
for _, c := range cs {
|
||||
runner := &runner{
|
||||
checker: c,
|
||||
tags: opt.Tags,
|
||||
ignores: ignores,
|
||||
version: opt.GoVersion,
|
||||
returnIgnored: opt.ReturnIgnored,
|
||||
var problems []lint.Problem
|
||||
workingPkgs := make([]*packages.Package, 0, len(pkgs))
|
||||
for _, pkg := range pkgs {
|
||||
if pkg.IllTyped {
|
||||
problems = append(problems, compileErrors(pkg)...)
|
||||
} else {
|
||||
workingPkgs = append(workingPkgs, pkg)
|
||||
}
|
||||
problems = append(problems, runner.lint(lprog, conf))
|
||||
}
|
||||
|
||||
if len(workingPkgs) == 0 {
|
||||
return problems, nil
|
||||
}
|
||||
|
||||
l := &lint.Linter{
|
||||
Checkers: cs,
|
||||
Ignores: ignores,
|
||||
GoVersion: opt.GoVersion,
|
||||
ReturnIgnored: opt.ReturnIgnored,
|
||||
Config: opt.Config,
|
||||
|
||||
MaxConcurrentJobs: opt.MaxConcurrentJobs,
|
||||
PrintStats: opt.PrintStats,
|
||||
}
|
||||
problems = append(problems, l.Lint(workingPkgs, &stats)...)
|
||||
|
||||
return problems, nil
|
||||
}
|
||||
|
||||
func shortPath(path string) string {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return path
|
||||
var posRe = regexp.MustCompile(`^(.+?):(\d+)(?::(\d+)?)?$`)
|
||||
|
||||
func parsePos(pos string) token.Position {
|
||||
if pos == "-" || pos == "" {
|
||||
return token.Position{}
|
||||
}
|
||||
if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) {
|
||||
return rel
|
||||
parts := posRe.FindStringSubmatch(pos)
|
||||
if parts == nil {
|
||||
panic(fmt.Sprintf("internal error: malformed position %q", pos))
|
||||
}
|
||||
file := parts[1]
|
||||
line, _ := strconv.Atoi(parts[2])
|
||||
col, _ := strconv.Atoi(parts[3])
|
||||
return token.Position{
|
||||
Filename: file,
|
||||
Line: line,
|
||||
Column: col,
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func relativePositionString(pos token.Position) string {
|
||||
s := shortPath(pos.Filename)
|
||||
if pos.IsValid() {
|
||||
if s != "" {
|
||||
s += ":"
|
||||
func compileErrors(pkg *packages.Package) []lint.Problem {
|
||||
if !pkg.IllTyped {
|
||||
return nil
|
||||
}
|
||||
if len(pkg.Errors) == 0 {
|
||||
// transitively ill-typed
|
||||
var ps []lint.Problem
|
||||
for _, imp := range pkg.Imports {
|
||||
ps = append(ps, compileErrors(imp)...)
|
||||
}
|
||||
s += fmt.Sprintf("%d:%d", pos.Line, pos.Column)
|
||||
return ps
|
||||
}
|
||||
if s == "" {
|
||||
s = "-"
|
||||
var ps []lint.Problem
|
||||
for _, err := range pkg.Errors {
|
||||
p := lint.Problem{
|
||||
Position: parsePos(err.Pos),
|
||||
Text: err.Msg,
|
||||
Checker: "compiler",
|
||||
Check: "compile",
|
||||
}
|
||||
ps = append(ps, p)
|
||||
}
|
||||
return s
|
||||
return ps
|
||||
}
|
||||
|
||||
func ProcessArgs(name string, cs []CheckerConfig, args []string) {
|
||||
func ProcessArgs(name string, cs []lint.Checker, args []string) {
|
||||
flags := FlagSet(name)
|
||||
flags.Parse(args)
|
||||
|
||||
ProcessFlagSet(cs, flags)
|
||||
}
|
||||
|
||||
func (runner *runner) lint(lprog *loader.Program, conf *loader.Config) []lint.Problem {
|
||||
l := &lint.Linter{
|
||||
Checker: runner.checker,
|
||||
Ignores: runner.ignores,
|
||||
GoVersion: runner.version,
|
||||
ReturnIgnored: runner.returnIgnored,
|
||||
}
|
||||
return l.Lint(lprog, conf)
|
||||
}
|
||||
|
666
vendor/honnef.co/go/tools/simple/lint.go
vendored
666
vendor/honnef.co/go/tools/simple/lint.go
vendored
File diff suppressed because it is too large
Load Diff
7
vendor/honnef.co/go/tools/simple/lint17.go
vendored
7
vendor/honnef.co/go/tools/simple/lint17.go
vendored
@ -1,7 +0,0 @@
|
||||
// +build !go1.8
|
||||
|
||||
package simple
|
||||
|
||||
import "go/types"
|
||||
|
||||
var structsIdentical = types.Identical
|
7
vendor/honnef.co/go/tools/simple/lint18.go
vendored
7
vendor/honnef.co/go/tools/simple/lint18.go
vendored
@ -1,7 +0,0 @@
|
||||
// +build go1.8
|
||||
|
||||
package simple
|
||||
|
||||
import "go/types"
|
||||
|
||||
var structsIdentical = types.IdenticalIgnoreTags
|
48
vendor/honnef.co/go/tools/ssa/ssautil/load.go
vendored
48
vendor/honnef.co/go/tools/ssa/ssautil/load.go
vendored
@ -12,9 +12,57 @@ import (
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"honnef.co/go/tools/ssa"
|
||||
)
|
||||
|
||||
// Packages creates an SSA program for a set of packages loaded from
|
||||
// source syntax using the golang.org/x/tools/go/packages.Load function.
|
||||
// It creates and returns an SSA package for each well-typed package in
|
||||
// the initial list. The resulting list of packages has the same length
|
||||
// as initial, and contains a nil if SSA could not be constructed for
|
||||
// the corresponding initial package.
|
||||
//
|
||||
// Code for bodies of functions is not built until Build is called
|
||||
// on the resulting Program.
|
||||
//
|
||||
// The mode parameter controls diagnostics and checking during SSA construction.
|
||||
//
|
||||
func Packages(initial []*packages.Package, mode ssa.BuilderMode) (*ssa.Program, []*ssa.Package) {
|
||||
var fset *token.FileSet
|
||||
if len(initial) > 0 {
|
||||
fset = initial[0].Fset
|
||||
}
|
||||
|
||||
prog := ssa.NewProgram(fset, mode)
|
||||
seen := make(map[*packages.Package]*ssa.Package)
|
||||
var create func(p *packages.Package) *ssa.Package
|
||||
create = func(p *packages.Package) *ssa.Package {
|
||||
ssapkg, ok := seen[p]
|
||||
if !ok {
|
||||
if p.Types == nil || p.IllTyped {
|
||||
// not well typed
|
||||
seen[p] = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
ssapkg = prog.CreatePackage(p.Types, p.Syntax, p.TypesInfo, true)
|
||||
seen[p] = ssapkg
|
||||
|
||||
for _, imp := range p.Imports {
|
||||
create(imp)
|
||||
}
|
||||
}
|
||||
return ssapkg
|
||||
}
|
||||
|
||||
var ssapkgs []*ssa.Package
|
||||
for _, p := range initial {
|
||||
ssapkgs = append(ssapkgs, create(p))
|
||||
}
|
||||
return prog, ssapkgs
|
||||
}
|
||||
|
||||
// CreateProgram returns a new program in SSA form, given a program
|
||||
// loaded from source. An SSA package is created for each transitively
|
||||
// error-free package of lprog.
|
||||
|
41
vendor/honnef.co/go/tools/ssautil/ssautil.go
vendored
Normal file
41
vendor/honnef.co/go/tools/ssautil/ssautil.go
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
package ssautil
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/ssa"
|
||||
)
|
||||
|
||||
func Reachable(from, to *ssa.BasicBlock) bool {
|
||||
if from == to {
|
||||
return true
|
||||
}
|
||||
if from.Dominates(to) {
|
||||
return true
|
||||
}
|
||||
|
||||
found := false
|
||||
Walk(from, func(b *ssa.BasicBlock) bool {
|
||||
if b == to {
|
||||
found = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
func Walk(b *ssa.BasicBlock, fn func(*ssa.BasicBlock) bool) {
|
||||
seen := map[*ssa.BasicBlock]bool{}
|
||||
wl := []*ssa.BasicBlock{b}
|
||||
for len(wl) > 0 {
|
||||
b := wl[len(wl)-1]
|
||||
wl = wl[:len(wl)-1]
|
||||
if seen[b] {
|
||||
continue
|
||||
}
|
||||
seen[b] = true
|
||||
if !fn(b) {
|
||||
continue
|
||||
}
|
||||
wl = append(wl, b.Succs...)
|
||||
}
|
||||
}
|
747
vendor/honnef.co/go/tools/staticcheck/lint.go
vendored
747
vendor/honnef.co/go/tools/staticcheck/lint.go
vendored
File diff suppressed because it is too large
Load Diff
643
vendor/honnef.co/go/tools/stylecheck/lint.go
vendored
Normal file
643
vendor/honnef.co/go/tools/stylecheck/lint.go
vendored
Normal file
@ -0,0 +1,643 @@
|
||||
package stylecheck // import "honnef.co/go/tools/stylecheck"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"honnef.co/go/tools/lint"
|
||||
. "honnef.co/go/tools/lint/lintdsl"
|
||||
"honnef.co/go/tools/ssa"
|
||||
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
type Checker struct {
|
||||
CheckGenerated bool
|
||||
}
|
||||
|
||||
func NewChecker() *Checker {
|
||||
return &Checker{}
|
||||
}
|
||||
|
||||
func (*Checker) Name() string { return "stylecheck" }
|
||||
func (*Checker) Prefix() string { return "ST" }
|
||||
func (c *Checker) Init(prog *lint.Program) {}
|
||||
|
||||
func (c *Checker) Checks() []lint.Check {
|
||||
return []lint.Check{
|
||||
{ID: "ST1000", FilterGenerated: false, Fn: c.CheckPackageComment},
|
||||
{ID: "ST1001", FilterGenerated: true, Fn: c.CheckDotImports},
|
||||
// {ID: "ST1002", FilterGenerated: true, Fn: c.CheckBlankImports},
|
||||
{ID: "ST1003", FilterGenerated: true, Fn: c.CheckNames},
|
||||
// {ID: "ST1004", FilterGenerated: false, Fn: nil, },
|
||||
{ID: "ST1005", FilterGenerated: false, Fn: c.CheckErrorStrings},
|
||||
{ID: "ST1006", FilterGenerated: false, Fn: c.CheckReceiverNames},
|
||||
// {ID: "ST1007", FilterGenerated: true, Fn: c.CheckIncDec},
|
||||
{ID: "ST1008", FilterGenerated: false, Fn: c.CheckErrorReturn},
|
||||
// {ID: "ST1009", FilterGenerated: false, Fn: c.CheckUnexportedReturn},
|
||||
// {ID: "ST1010", FilterGenerated: false, Fn: c.CheckContextFirstArg},
|
||||
{ID: "ST1011", FilterGenerated: false, Fn: c.CheckTimeNames},
|
||||
{ID: "ST1012", FilterGenerated: false, Fn: c.CheckErrorVarNames},
|
||||
{ID: "ST1013", FilterGenerated: true, Fn: c.CheckHTTPStatusCodes},
|
||||
{ID: "ST1015", FilterGenerated: true, Fn: c.CheckDefaultCaseOrder},
|
||||
{ID: "ST1016", FilterGenerated: false, Fn: c.CheckReceiverNamesIdentical},
|
||||
{ID: "ST1017", FilterGenerated: true, Fn: c.CheckYodaConditions},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) CheckPackageComment(j *lint.Job) {
|
||||
// - At least one file in a non-main package should have a package comment
|
||||
//
|
||||
// - The comment should be of the form
|
||||
// "Package x ...". This has a slight potential for false
|
||||
// positives, as multiple files can have package comments, in
|
||||
// which case they get appended. But that doesn't happen a lot in
|
||||
// the real world.
|
||||
|
||||
for _, pkg := range j.Program.InitialPackages {
|
||||
if pkg.Name == "main" {
|
||||
continue
|
||||
}
|
||||
hasDocs := false
|
||||
for _, f := range pkg.Syntax {
|
||||
if IsInTest(j, f) {
|
||||
continue
|
||||
}
|
||||
if f.Doc != nil && len(f.Doc.List) > 0 {
|
||||
hasDocs = true
|
||||
prefix := "Package " + f.Name.Name + " "
|
||||
if !strings.HasPrefix(strings.TrimSpace(f.Doc.Text()), prefix) {
|
||||
j.Errorf(f.Doc, `package comment should be of the form "%s..."`, prefix)
|
||||
}
|
||||
f.Doc.Text()
|
||||
}
|
||||
}
|
||||
|
||||
if !hasDocs {
|
||||
for _, f := range pkg.Syntax {
|
||||
if IsInTest(j, f) {
|
||||
continue
|
||||
}
|
||||
j.Errorf(f, "at least one file in a package should have a package comment")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) CheckDotImports(j *lint.Job) {
|
||||
for _, pkg := range j.Program.InitialPackages {
|
||||
for _, f := range pkg.Syntax {
|
||||
imports:
|
||||
for _, imp := range f.Imports {
|
||||
path := imp.Path.Value
|
||||
path = path[1 : len(path)-1]
|
||||
for _, w := range pkg.Config.DotImportWhitelist {
|
||||
if w == path {
|
||||
continue imports
|
||||
}
|
||||
}
|
||||
|
||||
if imp.Name != nil && imp.Name.Name == "." && !IsInTest(j, f) {
|
||||
j.Errorf(imp, "should not use dot imports")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) CheckBlankImports(j *lint.Job) {
|
||||
fset := j.Program.Fset()
|
||||
for _, f := range j.Program.Files {
|
||||
if IsInMain(j, f) || IsInTest(j, f) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Collect imports of the form `import _ "foo"`, i.e. with no
|
||||
// parentheses, as their comment will be associated with the
|
||||
// (paren-free) GenDecl, not the import spec itself.
|
||||
//
|
||||
// We don't directly process the GenDecl so that we can
|
||||
// correctly handle the following:
|
||||
//
|
||||
// import _ "foo"
|
||||
// import _ "bar"
|
||||
//
|
||||
// where only the first import should get flagged.
|
||||
skip := map[ast.Spec]bool{}
|
||||
ast.Inspect(f, func(node ast.Node) bool {
|
||||
switch node := node.(type) {
|
||||
case *ast.File:
|
||||
return true
|
||||
case *ast.GenDecl:
|
||||
if node.Tok != token.IMPORT {
|
||||
return false
|
||||
}
|
||||
if node.Lparen == token.NoPos && node.Doc != nil {
|
||||
skip[node.Specs[0]] = true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
})
|
||||
for i, imp := range f.Imports {
|
||||
pos := fset.Position(imp.Pos())
|
||||
|
||||
if !IsBlank(imp.Name) {
|
||||
continue
|
||||
}
|
||||
// Only flag the first blank import in a group of imports,
|
||||
// or don't flag any of them, if the first one is
|
||||
// commented
|
||||
if i > 0 {
|
||||
prev := f.Imports[i-1]
|
||||
prevPos := fset.Position(prev.Pos())
|
||||
if pos.Line-1 == prevPos.Line && IsBlank(prev.Name) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if imp.Doc == nil && imp.Comment == nil && !skip[imp] {
|
||||
j.Errorf(imp, "a blank import should be only in a main or test package, or have a comment justifying it")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) CheckIncDec(j *lint.Job) {
|
||||
// TODO(dh): this can be noisy for function bodies that look like this:
|
||||
// x += 3
|
||||
// ...
|
||||
// x += 2
|
||||
// ...
|
||||
// x += 1
|
||||
fn := func(node ast.Node) bool {
|
||||
assign, ok := node.(*ast.AssignStmt)
|
||||
if !ok || (assign.Tok != token.ADD_ASSIGN && assign.Tok != token.SUB_ASSIGN) {
|
||||
return true
|
||||
}
|
||||
if (len(assign.Lhs) != 1 || len(assign.Rhs) != 1) ||
|
||||
!IsIntLiteral(assign.Rhs[0], "1") {
|
||||
return true
|
||||
}
|
||||
|
||||
suffix := ""
|
||||
switch assign.Tok {
|
||||
case token.ADD_ASSIGN:
|
||||
suffix = "++"
|
||||
case token.SUB_ASSIGN:
|
||||
suffix = "--"
|
||||
}
|
||||
|
||||
j.Errorf(assign, "should replace %s with %s%s", Render(j, assign), Render(j, assign.Lhs[0]), suffix)
|
||||
return true
|
||||
}
|
||||
for _, f := range j.Program.Files {
|
||||
ast.Inspect(f, fn)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) CheckErrorReturn(j *lint.Job) {
|
||||
fnLoop:
|
||||
for _, fn := range j.Program.InitialFunctions {
|
||||
sig := fn.Type().(*types.Signature)
|
||||
rets := sig.Results()
|
||||
if rets == nil || rets.Len() < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
if rets.At(rets.Len()-1).Type() == types.Universe.Lookup("error").Type() {
|
||||
// Last return type is error. If the function also returns
|
||||
// errors in other positions, that's fine.
|
||||
continue
|
||||
}
|
||||
for i := rets.Len() - 2; i >= 0; i-- {
|
||||
if rets.At(i).Type() == types.Universe.Lookup("error").Type() {
|
||||
j.Errorf(rets.At(i), "error should be returned as the last argument")
|
||||
continue fnLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CheckUnexportedReturn checks that exported functions on exported
|
||||
// types do not return unexported types.
|
||||
func (c *Checker) CheckUnexportedReturn(j *lint.Job) {
|
||||
for _, fn := range j.Program.InitialFunctions {
|
||||
if fn.Synthetic != "" || fn.Parent() != nil {
|
||||
continue
|
||||
}
|
||||
if !ast.IsExported(fn.Name()) || IsInMain(j, fn) || IsInTest(j, fn) {
|
||||
continue
|
||||
}
|
||||
sig := fn.Type().(*types.Signature)
|
||||
if sig.Recv() != nil && !ast.IsExported(Dereference(sig.Recv().Type()).(*types.Named).Obj().Name()) {
|
||||
continue
|
||||
}
|
||||
res := sig.Results()
|
||||
for i := 0; i < res.Len(); i++ {
|
||||
if named, ok := DereferenceR(res.At(i).Type()).(*types.Named); ok &&
|
||||
!ast.IsExported(named.Obj().Name()) &&
|
||||
named != types.Universe.Lookup("error").Type() {
|
||||
j.Errorf(fn, "should not return unexported type")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) CheckReceiverNames(j *lint.Job) {
|
||||
for _, pkg := range j.Program.InitialPackages {
|
||||
for _, m := range pkg.SSA.Members {
|
||||
if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
|
||||
ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
|
||||
for _, sel := range ms {
|
||||
fn := sel.Obj().(*types.Func)
|
||||
recv := fn.Type().(*types.Signature).Recv()
|
||||
if Dereference(recv.Type()) != T.Type() {
|
||||
// skip embedded methods
|
||||
continue
|
||||
}
|
||||
if recv.Name() == "self" || recv.Name() == "this" {
|
||||
j.Errorf(recv, `receiver name should be a reflection of its identity; don't use generic names such as "this" or "self"`)
|
||||
}
|
||||
if recv.Name() == "_" {
|
||||
j.Errorf(recv, "receiver name should not be an underscore, omit the name if it is unused")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) CheckReceiverNamesIdentical(j *lint.Job) {
|
||||
for _, pkg := range j.Program.InitialPackages {
|
||||
for _, m := range pkg.SSA.Members {
|
||||
names := map[string]int{}
|
||||
|
||||
var firstFn *types.Func
|
||||
if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
|
||||
ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
|
||||
for _, sel := range ms {
|
||||
fn := sel.Obj().(*types.Func)
|
||||
recv := fn.Type().(*types.Signature).Recv()
|
||||
if Dereference(recv.Type()) != T.Type() {
|
||||
// skip embedded methods
|
||||
continue
|
||||
}
|
||||
if firstFn == nil {
|
||||
firstFn = fn
|
||||
}
|
||||
if recv.Name() != "" && recv.Name() != "_" {
|
||||
names[recv.Name()]++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(names) > 1 {
|
||||
var seen []string
|
||||
for name, count := range names {
|
||||
seen = append(seen, fmt.Sprintf("%dx %q", count, name))
|
||||
}
|
||||
|
||||
j.Errorf(firstFn, "methods on the same type should have the same receiver name (seen %s)", strings.Join(seen, ", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) CheckContextFirstArg(j *lint.Job) {
|
||||
// TODO(dh): this check doesn't apply to test helpers. Example from the stdlib:
|
||||
// func helperCommandContext(t *testing.T, ctx context.Context, s ...string) (cmd *exec.Cmd) {
|
||||
fnLoop:
|
||||
for _, fn := range j.Program.InitialFunctions {
|
||||
if fn.Synthetic != "" || fn.Parent() != nil {
|
||||
continue
|
||||
}
|
||||
params := fn.Signature.Params()
|
||||
if params.Len() < 2 {
|
||||
continue
|
||||
}
|
||||
if types.TypeString(params.At(0).Type(), nil) == "context.Context" {
|
||||
continue
|
||||
}
|
||||
for i := 1; i < params.Len(); i++ {
|
||||
param := params.At(i)
|
||||
if types.TypeString(param.Type(), nil) == "context.Context" {
|
||||
j.Errorf(param, "context.Context should be the first argument of a function")
|
||||
continue fnLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) CheckErrorStrings(j *lint.Job) {
|
||||
fnNames := map[*ssa.Package]map[string]bool{}
|
||||
for _, fn := range j.Program.InitialFunctions {
|
||||
m := fnNames[fn.Package()]
|
||||
if m == nil {
|
||||
m = map[string]bool{}
|
||||
fnNames[fn.Package()] = m
|
||||
}
|
||||
m[fn.Name()] = true
|
||||
}
|
||||
|
||||
for _, fn := range j.Program.InitialFunctions {
|
||||
if IsInTest(j, fn) {
|
||||
// We don't care about malformed error messages in tests;
|
||||
// they're usually for direct human consumption, not part
|
||||
// of an API
|
||||
continue
|
||||
}
|
||||
for _, block := range fn.Blocks {
|
||||
instrLoop:
|
||||
for _, ins := range block.Instrs {
|
||||
call, ok := ins.(*ssa.Call)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !IsCallTo(call.Common(), "errors.New") && !IsCallTo(call.Common(), "fmt.Errorf") {
|
||||
continue
|
||||
}
|
||||
|
||||
k, ok := call.Common().Args[0].(*ssa.Const)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
s := constant.StringVal(k.Value)
|
||||
if len(s) == 0 {
|
||||
continue
|
||||
}
|
||||
switch s[len(s)-1] {
|
||||
case '.', ':', '!', '\n':
|
||||
j.Errorf(call, "error strings should not end with punctuation or a newline")
|
||||
}
|
||||
idx := strings.IndexByte(s, ' ')
|
||||
if idx == -1 {
|
||||
// single word error message, probably not a real
|
||||
// error but something used in tests or during
|
||||
// debugging
|
||||
continue
|
||||
}
|
||||
word := s[:idx]
|
||||
first, n := utf8.DecodeRuneInString(word)
|
||||
if !unicode.IsUpper(first) {
|
||||
continue
|
||||
}
|
||||
for _, c := range word[n:] {
|
||||
if unicode.IsUpper(c) {
|
||||
// Word is probably an initialism or
|
||||
// multi-word function name
|
||||
continue instrLoop
|
||||
}
|
||||
}
|
||||
|
||||
word = strings.TrimRightFunc(word, func(r rune) bool { return unicode.IsPunct(r) })
|
||||
if fnNames[fn.Package()][word] {
|
||||
// Word is probably the name of a function in this package
|
||||
continue
|
||||
}
|
||||
// First word in error starts with a capital
|
||||
// letter, and the word doesn't contain any other
|
||||
// capitals, making it unlikely to be an
|
||||
// initialism or multi-word function name.
|
||||
//
|
||||
// It could still be a proper noun, though.
|
||||
|
||||
j.Errorf(call, "error strings should not be capitalized")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) CheckTimeNames(j *lint.Job) {
|
||||
suffixes := []string{
|
||||
"Sec", "Secs", "Seconds",
|
||||
"Msec", "Msecs",
|
||||
"Milli", "Millis", "Milliseconds",
|
||||
"Usec", "Usecs", "Microseconds",
|
||||
"MS", "Ms",
|
||||
}
|
||||
fn := func(T types.Type, names []*ast.Ident) {
|
||||
if !IsType(T, "time.Duration") && !IsType(T, "*time.Duration") {
|
||||
return
|
||||
}
|
||||
for _, name := range names {
|
||||
for _, suffix := range suffixes {
|
||||
if strings.HasSuffix(name.Name, suffix) {
|
||||
j.Errorf(name, "var %s is of type %v; don't use unit-specific suffix %q", name.Name, T, suffix)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, f := range j.Program.Files {
|
||||
ast.Inspect(f, func(node ast.Node) bool {
|
||||
switch node := node.(type) {
|
||||
case *ast.ValueSpec:
|
||||
T := TypeOf(j, node.Type)
|
||||
fn(T, node.Names)
|
||||
case *ast.FieldList:
|
||||
for _, field := range node.List {
|
||||
T := TypeOf(j, field.Type)
|
||||
fn(T, field.Names)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) CheckErrorVarNames(j *lint.Job) {
|
||||
for _, f := range j.Program.Files {
|
||||
for _, decl := range f.Decls {
|
||||
gen, ok := decl.(*ast.GenDecl)
|
||||
if !ok || gen.Tok != token.VAR {
|
||||
continue
|
||||
}
|
||||
for _, spec := range gen.Specs {
|
||||
spec := spec.(*ast.ValueSpec)
|
||||
if len(spec.Names) != len(spec.Values) {
|
||||
continue
|
||||
}
|
||||
|
||||
for i, name := range spec.Names {
|
||||
val := spec.Values[i]
|
||||
if !IsCallToAST(j, val, "errors.New") && !IsCallToAST(j, val, "fmt.Errorf") {
|
||||
continue
|
||||
}
|
||||
|
||||
prefix := "err"
|
||||
if name.IsExported() {
|
||||
prefix = "Err"
|
||||
}
|
||||
if !strings.HasPrefix(name.Name, prefix) {
|
||||
j.Errorf(name, "error var %s should have name of the form %sFoo", name.Name, prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var httpStatusCodes = map[int]string{
|
||||
100: "StatusContinue",
|
||||
101: "StatusSwitchingProtocols",
|
||||
102: "StatusProcessing",
|
||||
200: "StatusOK",
|
||||
201: "StatusCreated",
|
||||
202: "StatusAccepted",
|
||||
203: "StatusNonAuthoritativeInfo",
|
||||
204: "StatusNoContent",
|
||||
205: "StatusResetContent",
|
||||
206: "StatusPartialContent",
|
||||
207: "StatusMultiStatus",
|
||||
208: "StatusAlreadyReported",
|
||||
226: "StatusIMUsed",
|
||||
300: "StatusMultipleChoices",
|
||||
301: "StatusMovedPermanently",
|
||||
302: "StatusFound",
|
||||
303: "StatusSeeOther",
|
||||
304: "StatusNotModified",
|
||||
305: "StatusUseProxy",
|
||||
307: "StatusTemporaryRedirect",
|
||||
308: "StatusPermanentRedirect",
|
||||
400: "StatusBadRequest",
|
||||
401: "StatusUnauthorized",
|
||||
402: "StatusPaymentRequired",
|
||||
403: "StatusForbidden",
|
||||
404: "StatusNotFound",
|
||||
405: "StatusMethodNotAllowed",
|
||||
406: "StatusNotAcceptable",
|
||||
407: "StatusProxyAuthRequired",
|
||||
408: "StatusRequestTimeout",
|
||||
409: "StatusConflict",
|
||||
410: "StatusGone",
|
||||
411: "StatusLengthRequired",
|
||||
412: "StatusPreconditionFailed",
|
||||
413: "StatusRequestEntityTooLarge",
|
||||
414: "StatusRequestURITooLong",
|
||||
415: "StatusUnsupportedMediaType",
|
||||
416: "StatusRequestedRangeNotSatisfiable",
|
||||
417: "StatusExpectationFailed",
|
||||
418: "StatusTeapot",
|
||||
422: "StatusUnprocessableEntity",
|
||||
423: "StatusLocked",
|
||||
424: "StatusFailedDependency",
|
||||
426: "StatusUpgradeRequired",
|
||||
428: "StatusPreconditionRequired",
|
||||
429: "StatusTooManyRequests",
|
||||
431: "StatusRequestHeaderFieldsTooLarge",
|
||||
451: "StatusUnavailableForLegalReasons",
|
||||
500: "StatusInternalServerError",
|
||||
501: "StatusNotImplemented",
|
||||
502: "StatusBadGateway",
|
||||
503: "StatusServiceUnavailable",
|
||||
504: "StatusGatewayTimeout",
|
||||
505: "StatusHTTPVersionNotSupported",
|
||||
506: "StatusVariantAlsoNegotiates",
|
||||
507: "StatusInsufficientStorage",
|
||||
508: "StatusLoopDetected",
|
||||
510: "StatusNotExtended",
|
||||
511: "StatusNetworkAuthenticationRequired",
|
||||
}
|
||||
|
||||
func (c *Checker) CheckHTTPStatusCodes(j *lint.Job) {
|
||||
for _, pkg := range j.Program.InitialPackages {
|
||||
whitelist := map[string]bool{}
|
||||
for _, code := range pkg.Config.HTTPStatusCodeWhitelist {
|
||||
whitelist[code] = true
|
||||
}
|
||||
fn := func(node ast.Node) bool {
|
||||
call, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
var arg int
|
||||
switch CallNameAST(j, call) {
|
||||
case "net/http.Error":
|
||||
arg = 2
|
||||
case "net/http.Redirect":
|
||||
arg = 3
|
||||
case "net/http.StatusText":
|
||||
arg = 0
|
||||
case "net/http.RedirectHandler":
|
||||
arg = 1
|
||||
default:
|
||||
return true
|
||||
}
|
||||
lit, ok := call.Args[arg].(*ast.BasicLit)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if whitelist[lit.Value] {
|
||||
return true
|
||||
}
|
||||
|
||||
n, err := strconv.Atoi(lit.Value)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
s, ok := httpStatusCodes[n]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
j.Errorf(lit, "should use constant http.%s instead of numeric literal %d", s, n)
|
||||
return true
|
||||
}
|
||||
for _, f := range pkg.Syntax {
|
||||
ast.Inspect(f, fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) CheckDefaultCaseOrder(j *lint.Job) {
|
||||
fn := func(node ast.Node) bool {
|
||||
stmt, ok := node.(*ast.SwitchStmt)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
list := stmt.Body.List
|
||||
for i, c := range list {
|
||||
if c.(*ast.CaseClause).List == nil && i != 0 && i != len(list)-1 {
|
||||
j.Errorf(c, "default case should be first or last in switch statement")
|
||||
break
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
for _, f := range j.Program.Files {
|
||||
ast.Inspect(f, fn)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) CheckYodaConditions(j *lint.Job) {
|
||||
fn := func(node ast.Node) bool {
|
||||
cond, ok := node.(*ast.BinaryExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if cond.Op != token.EQL && cond.Op != token.NEQ {
|
||||
return true
|
||||
}
|
||||
if _, ok := cond.X.(*ast.BasicLit); !ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := cond.Y.(*ast.BasicLit); ok {
|
||||
// Don't flag lit == lit conditions, just in case
|
||||
return true
|
||||
}
|
||||
j.Errorf(cond, "don't use Yoda conditions")
|
||||
return true
|
||||
}
|
||||
for _, f := range j.Program.Files {
|
||||
ast.Inspect(f, fn)
|
||||
}
|
||||
}
|
263
vendor/honnef.co/go/tools/stylecheck/names.go
vendored
Normal file
263
vendor/honnef.co/go/tools/stylecheck/names.go
vendored
Normal file
@ -0,0 +1,263 @@
|
||||
// Copyright (c) 2013 The Go Authors. All rights reserved.
|
||||
// Copyright (c) 2018 Dominik Honnef. All rights reserved.
|
||||
|
||||
package stylecheck
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"honnef.co/go/tools/lint"
|
||||
. "honnef.co/go/tools/lint/lintdsl"
|
||||
)
|
||||
|
||||
// knownNameExceptions is a set of names that are known to be exempt from naming checks.
|
||||
// This is usually because they are constrained by having to match names in the
|
||||
// standard library.
|
||||
var knownNameExceptions = map[string]bool{
|
||||
"LastInsertId": true, // must match database/sql
|
||||
"kWh": true,
|
||||
}
|
||||
|
||||
func (c *Checker) CheckNames(j *lint.Job) {
|
||||
// A large part of this function is copied from
|
||||
// github.com/golang/lint, Copyright (c) 2013 The Go Authors,
|
||||
// licensed under the BSD 3-clause license.
|
||||
|
||||
allCaps := func(s string) bool {
|
||||
for _, r := range s {
|
||||
if !((r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
check := func(id *ast.Ident, thing string, initialisms map[string]bool) {
|
||||
if id.Name == "_" {
|
||||
return
|
||||
}
|
||||
if knownNameExceptions[id.Name] {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle two common styles from other languages that don't belong in Go.
|
||||
if len(id.Name) >= 5 && allCaps(id.Name) && strings.Contains(id.Name, "_") {
|
||||
j.Errorf(id, "should not use ALL_CAPS in Go names; use CamelCase instead")
|
||||
return
|
||||
}
|
||||
|
||||
should := lintName(id.Name, initialisms)
|
||||
if id.Name == should {
|
||||
return
|
||||
}
|
||||
|
||||
if len(id.Name) > 2 && strings.Contains(id.Name[1:len(id.Name)-1], "_") {
|
||||
j.Errorf(id, "should not use underscores in Go names; %s %s should be %s", thing, id.Name, should)
|
||||
return
|
||||
}
|
||||
j.Errorf(id, "%s %s should be %s", thing, id.Name, should)
|
||||
}
|
||||
checkList := func(fl *ast.FieldList, thing string, initialisms map[string]bool) {
|
||||
if fl == nil {
|
||||
return
|
||||
}
|
||||
for _, f := range fl.List {
|
||||
for _, id := range f.Names {
|
||||
check(id, thing, initialisms)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, pkg := range j.Program.InitialPackages {
|
||||
initialisms := make(map[string]bool, len(pkg.Config.Initialisms))
|
||||
for _, word := range pkg.Config.Initialisms {
|
||||
initialisms[word] = true
|
||||
}
|
||||
for _, f := range pkg.Syntax {
|
||||
// Package names need slightly different handling than other names.
|
||||
if !strings.HasSuffix(f.Name.Name, "_test") && strings.Contains(f.Name.Name, "_") {
|
||||
j.Errorf(f, "should not use underscores in package names")
|
||||
}
|
||||
if strings.IndexFunc(f.Name.Name, unicode.IsUpper) != -1 {
|
||||
j.Errorf(f, "should not use MixedCaps in package name; %s should be %s", f.Name.Name, strings.ToLower(f.Name.Name))
|
||||
}
|
||||
|
||||
ast.Inspect(f, func(node ast.Node) bool {
|
||||
switch v := node.(type) {
|
||||
case *ast.AssignStmt:
|
||||
if v.Tok != token.DEFINE {
|
||||
return true
|
||||
}
|
||||
for _, exp := range v.Lhs {
|
||||
if id, ok := exp.(*ast.Ident); ok {
|
||||
check(id, "var", initialisms)
|
||||
}
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
// Functions with no body are defined elsewhere (in
|
||||
// assembly, or via go:linkname). These are likely to
|
||||
// be something very low level (such as the runtime),
|
||||
// where our rules don't apply.
|
||||
if v.Body == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if IsInTest(j, v) && (strings.HasPrefix(v.Name.Name, "Example") || strings.HasPrefix(v.Name.Name, "Test") || strings.HasPrefix(v.Name.Name, "Benchmark")) {
|
||||
return true
|
||||
}
|
||||
|
||||
thing := "func"
|
||||
if v.Recv != nil {
|
||||
thing = "method"
|
||||
}
|
||||
|
||||
if !isTechnicallyExported(v) {
|
||||
check(v.Name, thing, initialisms)
|
||||
}
|
||||
|
||||
checkList(v.Type.Params, thing+" parameter", initialisms)
|
||||
checkList(v.Type.Results, thing+" result", initialisms)
|
||||
case *ast.GenDecl:
|
||||
if v.Tok == token.IMPORT {
|
||||
return true
|
||||
}
|
||||
var thing string
|
||||
switch v.Tok {
|
||||
case token.CONST:
|
||||
thing = "const"
|
||||
case token.TYPE:
|
||||
thing = "type"
|
||||
case token.VAR:
|
||||
thing = "var"
|
||||
}
|
||||
for _, spec := range v.Specs {
|
||||
switch s := spec.(type) {
|
||||
case *ast.TypeSpec:
|
||||
check(s.Name, thing, initialisms)
|
||||
case *ast.ValueSpec:
|
||||
for _, id := range s.Names {
|
||||
check(id, thing, initialisms)
|
||||
}
|
||||
}
|
||||
}
|
||||
case *ast.InterfaceType:
|
||||
// Do not check interface method names.
|
||||
// They are often constrainted by the method names of concrete types.
|
||||
for _, x := range v.Methods.List {
|
||||
ft, ok := x.Type.(*ast.FuncType)
|
||||
if !ok { // might be an embedded interface name
|
||||
continue
|
||||
}
|
||||
checkList(ft.Params, "interface method parameter", initialisms)
|
||||
checkList(ft.Results, "interface method result", initialisms)
|
||||
}
|
||||
case *ast.RangeStmt:
|
||||
if v.Tok == token.ASSIGN {
|
||||
return true
|
||||
}
|
||||
if id, ok := v.Key.(*ast.Ident); ok {
|
||||
check(id, "range var", initialisms)
|
||||
}
|
||||
if id, ok := v.Value.(*ast.Ident); ok {
|
||||
check(id, "range var", initialisms)
|
||||
}
|
||||
case *ast.StructType:
|
||||
for _, f := range v.Fields.List {
|
||||
for _, id := range f.Names {
|
||||
check(id, "struct field", initialisms)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lintName returns a different name if it should be different.
|
||||
func lintName(name string, initialisms map[string]bool) (should string) {
|
||||
// A large part of this function is copied from
|
||||
// github.com/golang/lint, Copyright (c) 2013 The Go Authors,
|
||||
// licensed under the BSD 3-clause license.
|
||||
|
||||
// Fast path for simple cases: "_" and all lowercase.
|
||||
if name == "_" {
|
||||
return name
|
||||
}
|
||||
if strings.IndexFunc(name, func(r rune) bool { return !unicode.IsLower(r) }) == -1 {
|
||||
return name
|
||||
}
|
||||
|
||||
// Split camelCase at any lower->upper transition, and split on underscores.
|
||||
// Check each word for common initialisms.
|
||||
runes := []rune(name)
|
||||
w, i := 0, 0 // index of start of word, scan
|
||||
for i+1 <= len(runes) {
|
||||
eow := false // whether we hit the end of a word
|
||||
if i+1 == len(runes) {
|
||||
eow = true
|
||||
} else if runes[i+1] == '_' && i+1 != len(runes)-1 {
|
||||
// underscore; shift the remainder forward over any run of underscores
|
||||
eow = true
|
||||
n := 1
|
||||
for i+n+1 < len(runes) && runes[i+n+1] == '_' {
|
||||
n++
|
||||
}
|
||||
|
||||
// Leave at most one underscore if the underscore is between two digits
|
||||
if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) {
|
||||
n--
|
||||
}
|
||||
|
||||
copy(runes[i+1:], runes[i+n+1:])
|
||||
runes = runes[:len(runes)-n]
|
||||
} else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) {
|
||||
// lower->non-lower
|
||||
eow = true
|
||||
}
|
||||
i++
|
||||
if !eow {
|
||||
continue
|
||||
}
|
||||
|
||||
// [w,i) is a word.
|
||||
word := string(runes[w:i])
|
||||
if u := strings.ToUpper(word); initialisms[u] {
|
||||
// Keep consistent case, which is lowercase only at the start.
|
||||
if w == 0 && unicode.IsLower(runes[w]) {
|
||||
u = strings.ToLower(u)
|
||||
}
|
||||
// All the common initialisms are ASCII,
|
||||
// so we can replace the bytes exactly.
|
||||
// TODO(dh): this won't be true once we allow custom initialisms
|
||||
copy(runes[w:], []rune(u))
|
||||
} else if w > 0 && strings.ToLower(word) == word {
|
||||
// already all lowercase, and not the first word, so uppercase the first character.
|
||||
runes[w] = unicode.ToUpper(runes[w])
|
||||
}
|
||||
w = i
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
func isTechnicallyExported(f *ast.FuncDecl) bool {
|
||||
if f.Recv != nil || f.Doc == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
const export = "//export "
|
||||
const linkname = "//go:linkname "
|
||||
for _, c := range f.Doc.List {
|
||||
if strings.HasPrefix(c.Text, export) && len(c.Text) == len(export)+len(f.Name.Name) && c.Text[len(export):] == f.Name.Name {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(c.Text, linkname) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
79
vendor/honnef.co/go/tools/unused/implements.go
vendored
Normal file
79
vendor/honnef.co/go/tools/unused/implements.go
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
package unused
|
||||
|
||||
import "go/types"
|
||||
|
||||
// lookupMethod returns the index of and method with matching package and name, or (-1, nil).
|
||||
func lookupMethod(T *types.Interface, pkg *types.Package, name string) (int, *types.Func) {
|
||||
if name != "_" {
|
||||
for i := 0; i < T.NumMethods(); i++ {
|
||||
m := T.Method(i)
|
||||
if sameId(m, pkg, name) {
|
||||
return i, m
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
func sameId(obj types.Object, pkg *types.Package, name string) bool {
|
||||
// spec:
|
||||
// "Two identifiers are different if they are spelled differently,
|
||||
// or if they appear in different packages and are not exported.
|
||||
// Otherwise, they are the same."
|
||||
if name != obj.Name() {
|
||||
return false
|
||||
}
|
||||
// obj.Name == name
|
||||
if obj.Exported() {
|
||||
return true
|
||||
}
|
||||
// not exported, so packages must be the same (pkg == nil for
|
||||
// fields in Universe scope; this can only happen for types
|
||||
// introduced via Eval)
|
||||
if pkg == nil || obj.Pkg() == nil {
|
||||
return pkg == obj.Pkg()
|
||||
}
|
||||
// pkg != nil && obj.pkg != nil
|
||||
return pkg.Path() == obj.Pkg().Path()
|
||||
}
|
||||
|
||||
func (c *Checker) implements(V types.Type, T *types.Interface) bool {
|
||||
// fast path for common case
|
||||
if T.Empty() {
|
||||
return true
|
||||
}
|
||||
|
||||
if ityp, _ := V.Underlying().(*types.Interface); ityp != nil {
|
||||
for i := 0; i < T.NumMethods(); i++ {
|
||||
m := T.Method(i)
|
||||
_, obj := lookupMethod(ityp, m.Pkg(), m.Name())
|
||||
switch {
|
||||
case obj == nil:
|
||||
return false
|
||||
case !types.Identical(obj.Type(), m.Type()):
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// A concrete type implements T if it implements all methods of T.
|
||||
ms := c.msCache.MethodSet(V)
|
||||
for i := 0; i < T.NumMethods(); i++ {
|
||||
m := T.Method(i)
|
||||
sel := ms.Lookup(m.Pkg(), m.Name())
|
||||
if sel == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
f, _ := sel.Obj().(*types.Func)
|
||||
if f == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if !types.Identical(f.Type(), m.Type()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
197
vendor/honnef.co/go/tools/unused/unused.go
vendored
197
vendor/honnef.co/go/tools/unused/unused.go
vendored
@ -12,7 +12,7 @@ import (
|
||||
"honnef.co/go/tools/lint"
|
||||
. "honnef.co/go/tools/lint/lintdsl"
|
||||
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
@ -31,9 +31,9 @@ func (*LintChecker) Name() string { return "unused" }
|
||||
func (*LintChecker) Prefix() string { return "U" }
|
||||
|
||||
func (l *LintChecker) Init(*lint.Program) {}
|
||||
func (l *LintChecker) Funcs() map[string]lint.Func {
|
||||
return map[string]lint.Func{
|
||||
"U1000": l.Lint,
|
||||
func (l *LintChecker) Checks() []lint.Check {
|
||||
return []lint.Check{
|
||||
{ID: "U1000", FilterGenerated: true, Fn: l.Lint},
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ func typString(obj types.Object) string {
|
||||
}
|
||||
|
||||
func (l *LintChecker) Lint(j *lint.Job) {
|
||||
unused := l.c.Check(j.Program.Prog)
|
||||
unused := l.c.Check(j.Program)
|
||||
for _, u := range unused {
|
||||
name := u.Obj.Name()
|
||||
if sig, ok := u.Obj.Type().(*types.Signature); ok && sig.Recv() != nil {
|
||||
@ -158,7 +158,7 @@ type Checker struct {
|
||||
graph *graph
|
||||
|
||||
msCache typeutil.MethodSetCache
|
||||
lprog *loader.Program
|
||||
prog *lint.Program
|
||||
topmostCache map[*types.Scope]*types.Scope
|
||||
interfaces []*types.Interface
|
||||
}
|
||||
@ -199,13 +199,13 @@ func (e Error) Error() string {
|
||||
return fmt.Sprintf("errors in %d packages", len(e.Errors))
|
||||
}
|
||||
|
||||
func (c *Checker) Check(lprog *loader.Program) []Unused {
|
||||
func (c *Checker) Check(prog *lint.Program) []Unused {
|
||||
var unused []Unused
|
||||
c.lprog = lprog
|
||||
c.prog = prog
|
||||
if c.WholeProgram {
|
||||
c.findExportedInterfaces()
|
||||
}
|
||||
for _, pkg := range c.lprog.InitialPackages() {
|
||||
for _, pkg := range prog.InitialPackages {
|
||||
c.processDefs(pkg)
|
||||
c.processUses(pkg)
|
||||
c.processTypes(pkg)
|
||||
@ -231,6 +231,7 @@ func (c *Checker) Check(lprog *loader.Program) []Unused {
|
||||
}
|
||||
markNodesUsed(roots)
|
||||
c.markNodesQuiet()
|
||||
c.deduplicate()
|
||||
|
||||
if c.Debug != nil {
|
||||
c.printDebugGraph(c.Debug)
|
||||
@ -246,8 +247,8 @@ func (c *Checker) Check(lprog *loader.Program) []Unused {
|
||||
}
|
||||
found := false
|
||||
if !false {
|
||||
for _, pkg := range c.lprog.InitialPackages() {
|
||||
if pkg.Pkg == obj.Pkg() {
|
||||
for _, pkg := range prog.InitialPackages {
|
||||
if pkg.Types == obj.Pkg() {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
@ -257,25 +258,14 @@ func (c *Checker) Check(lprog *loader.Program) []Unused {
|
||||
continue
|
||||
}
|
||||
|
||||
pos := c.lprog.Fset.Position(obj.Pos())
|
||||
pos := c.prog.Fset().Position(obj.Pos())
|
||||
if pos.Filename == "" || filepath.Base(pos.Filename) == "C" {
|
||||
continue
|
||||
}
|
||||
generated := false
|
||||
for _, file := range c.lprog.Package(obj.Pkg().Path()).Files {
|
||||
if c.lprog.Fset.Position(file.Pos()).Filename != pos.Filename {
|
||||
continue
|
||||
}
|
||||
if len(file.Comments) > 0 {
|
||||
generated = isGenerated(file.Comments[0].Text())
|
||||
}
|
||||
break
|
||||
}
|
||||
if generated {
|
||||
continue
|
||||
}
|
||||
|
||||
unused = append(unused, Unused{Obj: obj, Position: pos})
|
||||
}
|
||||
|
||||
return unused
|
||||
}
|
||||
|
||||
@ -325,16 +315,24 @@ func (c *Checker) useNoCopyFields(typ types.Type) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) useExportedFields(typ types.Type) {
|
||||
func (c *Checker) useExportedFields(typ types.Type, by types.Type) bool {
|
||||
any := false
|
||||
if st, ok := typ.Underlying().(*types.Struct); ok {
|
||||
n := st.NumFields()
|
||||
for i := 0; i < n; i++ {
|
||||
field := st.Field(i)
|
||||
if field.Anonymous() {
|
||||
if c.useExportedFields(field.Type(), typ) {
|
||||
c.graph.markUsedBy(field, typ)
|
||||
}
|
||||
}
|
||||
if field.Exported() {
|
||||
c.graph.markUsedBy(field, typ)
|
||||
c.graph.markUsedBy(field, by)
|
||||
any = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return any
|
||||
}
|
||||
|
||||
func (c *Checker) useExportedMethods(typ types.Type) {
|
||||
@ -370,8 +368,8 @@ func (c *Checker) useExportedMethods(typ types.Type) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) processDefs(pkg *loader.PackageInfo) {
|
||||
for _, obj := range pkg.Defs {
|
||||
func (c *Checker) processDefs(pkg *lint.Pkg) {
|
||||
for _, obj := range pkg.TypesInfo.Defs {
|
||||
if obj == nil {
|
||||
continue
|
||||
}
|
||||
@ -392,7 +390,7 @@ func (c *Checker) processDefs(pkg *loader.PackageInfo) {
|
||||
// mark them used if an instance of the type was
|
||||
// accessible via an interface value.
|
||||
if !c.WholeProgram || c.ConsiderReflection {
|
||||
c.useExportedFields(obj.Type())
|
||||
c.useExportedFields(obj.Type(), obj.Type())
|
||||
}
|
||||
|
||||
// TODO(dh): Traditionally we have not marked all exported
|
||||
@ -420,8 +418,8 @@ func (c *Checker) processDefs(pkg *loader.PackageInfo) {
|
||||
if obj.Name() == "_" {
|
||||
node := c.graph.getNode(obj)
|
||||
node.quiet = true
|
||||
scope := c.topmostScope(pkg.Pkg.Scope().Innermost(obj.Pos()), pkg.Pkg)
|
||||
if scope == pkg.Pkg.Scope() {
|
||||
scope := c.topmostScope(pkg.Types.Scope().Innermost(obj.Pos()), pkg.Types)
|
||||
if scope == pkg.Types.Scope() {
|
||||
c.graph.roots = append(c.graph.roots, node)
|
||||
} else {
|
||||
c.graph.markUsedBy(obj, scope)
|
||||
@ -471,15 +469,15 @@ func (c *Checker) processDefs(pkg *loader.PackageInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) processUses(pkg *loader.PackageInfo) {
|
||||
for ident, usedObj := range pkg.Uses {
|
||||
func (c *Checker) processUses(pkg *lint.Pkg) {
|
||||
for ident, usedObj := range pkg.TypesInfo.Uses {
|
||||
if _, ok := usedObj.(*types.PkgName); ok {
|
||||
continue
|
||||
}
|
||||
pos := ident.Pos()
|
||||
scope := pkg.Pkg.Scope().Innermost(pos)
|
||||
scope = c.topmostScope(scope, pkg.Pkg)
|
||||
if scope != pkg.Pkg.Scope() {
|
||||
scope := pkg.Types.Scope().Innermost(pos)
|
||||
scope = c.topmostScope(scope, pkg.Types)
|
||||
if scope != pkg.Types.Scope() {
|
||||
c.graph.markUsedBy(usedObj, scope)
|
||||
}
|
||||
|
||||
@ -492,17 +490,17 @@ func (c *Checker) processUses(pkg *loader.PackageInfo) {
|
||||
|
||||
func (c *Checker) findExportedInterfaces() {
|
||||
c.interfaces = []*types.Interface{types.Universe.Lookup("error").Type().(*types.Named).Underlying().(*types.Interface)}
|
||||
var pkgs []*loader.PackageInfo
|
||||
var pkgs []*packages.Package
|
||||
if c.WholeProgram {
|
||||
for _, pkg := range c.lprog.AllPackages {
|
||||
pkgs = append(pkgs, pkg)
|
||||
}
|
||||
pkgs = append(pkgs, c.prog.AllPackages...)
|
||||
} else {
|
||||
pkgs = c.lprog.InitialPackages()
|
||||
for _, pkg := range c.prog.InitialPackages {
|
||||
pkgs = append(pkgs, pkg.Package)
|
||||
}
|
||||
}
|
||||
|
||||
for _, pkg := range pkgs {
|
||||
for _, tv := range pkg.Types {
|
||||
for _, tv := range pkg.TypesInfo.Types {
|
||||
iface, ok := tv.Type.(*types.Interface)
|
||||
if !ok {
|
||||
continue
|
||||
@ -515,10 +513,10 @@ func (c *Checker) findExportedInterfaces() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) processTypes(pkg *loader.PackageInfo) {
|
||||
func (c *Checker) processTypes(pkg *lint.Pkg) {
|
||||
named := map[*types.Named]*types.Pointer{}
|
||||
var interfaces []*types.Interface
|
||||
for _, tv := range pkg.Types {
|
||||
for _, tv := range pkg.TypesInfo.Types {
|
||||
if typ, ok := tv.Type.(interface {
|
||||
Elem() types.Type
|
||||
}); ok {
|
||||
@ -536,8 +534,8 @@ func (c *Checker) processTypes(pkg *loader.PackageInfo) {
|
||||
}
|
||||
case *types.Struct:
|
||||
c.useNoCopyFields(obj)
|
||||
if pkg.Pkg.Name() != "main" && !c.WholeProgram {
|
||||
c.useExportedFields(obj)
|
||||
if pkg.Types.Name() != "main" && !c.WholeProgram {
|
||||
c.useExportedFields(obj, obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -547,15 +545,30 @@ func (c *Checker) processTypes(pkg *loader.PackageInfo) {
|
||||
//
|
||||
// TODO(dh): For normal operations, that's the best we can do, as
|
||||
// we have no idea what external users will do with our types. In
|
||||
// whole-program mode, we could be more conservative, in two ways:
|
||||
// whole-program mode, we could be more precise, in two ways:
|
||||
// 1) Only consider interfaces if a type has been assigned to one
|
||||
// 2) Use SSA and flow analysis and determine the exact set of
|
||||
// interfaces that is relevant.
|
||||
fn := func(iface *types.Interface) {
|
||||
for i := 0; i < iface.NumEmbeddeds(); i++ {
|
||||
c.graph.markUsedBy(iface.Embedded(i), iface)
|
||||
}
|
||||
namedLoop:
|
||||
for obj, objPtr := range named {
|
||||
if !types.Implements(obj, iface) && !types.Implements(objPtr, iface) {
|
||||
continue
|
||||
switch obj.Underlying().(type) {
|
||||
case *types.Interface:
|
||||
// pointers to interfaces have no methods, only checking non-pointer
|
||||
if !c.implements(obj, iface) {
|
||||
continue namedLoop
|
||||
}
|
||||
default:
|
||||
// pointer receivers include the method set of non-pointer receivers,
|
||||
// only checking pointer
|
||||
if !c.implements(objPtr, iface) {
|
||||
continue namedLoop
|
||||
}
|
||||
}
|
||||
|
||||
ifaceMethods := make(map[string]struct{}, iface.NumMethods())
|
||||
n := iface.NumMethods()
|
||||
for i := 0; i < n; i++ {
|
||||
@ -591,23 +604,23 @@ func (c *Checker) processTypes(pkg *loader.PackageInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) processSelections(pkg *loader.PackageInfo) {
|
||||
func (c *Checker) processSelections(pkg *lint.Pkg) {
|
||||
fn := func(expr *ast.SelectorExpr, sel *types.Selection, offset int) {
|
||||
scope := pkg.Pkg.Scope().Innermost(expr.Pos())
|
||||
c.graph.markUsedBy(expr.X, c.topmostScope(scope, pkg.Pkg))
|
||||
c.graph.markUsedBy(sel.Obj(), expr.X)
|
||||
scope := pkg.Types.Scope().Innermost(expr.Pos())
|
||||
c.graph.markUsedBy(sel, c.topmostScope(scope, pkg.Types))
|
||||
c.graph.markUsedBy(sel.Obj(), sel)
|
||||
if len(sel.Index()) > 1 {
|
||||
typ := sel.Recv()
|
||||
indices := sel.Index()
|
||||
for _, idx := range indices[:len(indices)-offset] {
|
||||
obj := getField(typ, idx)
|
||||
typ = obj.Type()
|
||||
c.graph.markUsedBy(obj, expr.X)
|
||||
c.graph.markUsedBy(obj, sel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for expr, sel := range pkg.Selections {
|
||||
for expr, sel := range pkg.TypesInfo.Selections {
|
||||
switch sel.Kind() {
|
||||
case types.FieldVal:
|
||||
fn(expr, sel, 0)
|
||||
@ -625,9 +638,9 @@ func dereferenceType(typ types.Type) types.Type {
|
||||
}
|
||||
|
||||
// processConversion marks fields as used if they're part of a type conversion.
|
||||
func (c *Checker) processConversion(pkg *loader.PackageInfo, node ast.Node) {
|
||||
func (c *Checker) processConversion(pkg *lint.Pkg, node ast.Node) {
|
||||
if node, ok := node.(*ast.CallExpr); ok {
|
||||
callTyp := pkg.TypeOf(node.Fun)
|
||||
callTyp := pkg.TypesInfo.TypeOf(node.Fun)
|
||||
var typDst *types.Struct
|
||||
var ok bool
|
||||
switch typ := callTyp.(type) {
|
||||
@ -642,7 +655,7 @@ func (c *Checker) processConversion(pkg *loader.PackageInfo, node ast.Node) {
|
||||
return
|
||||
}
|
||||
|
||||
if typ, ok := pkg.TypeOf(node.Args[0]).(*types.Basic); ok && typ.Kind() == types.UnsafePointer {
|
||||
if typ, ok := pkg.TypesInfo.TypeOf(node.Args[0]).(*types.Basic); ok && typ.Kind() == types.UnsafePointer {
|
||||
// This is an unsafe conversion. Assume that all the
|
||||
// fields are relevant (they are, because of memory
|
||||
// layout)
|
||||
@ -653,7 +666,7 @@ func (c *Checker) processConversion(pkg *loader.PackageInfo, node ast.Node) {
|
||||
return
|
||||
}
|
||||
|
||||
typSrc, ok := dereferenceType(pkg.TypeOf(node.Args[0])).Underlying().(*types.Struct)
|
||||
typSrc, ok := dereferenceType(pkg.TypesInfo.TypeOf(node.Args[0])).Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@ -683,10 +696,10 @@ func (c *Checker) processConversion(pkg *loader.PackageInfo, node ast.Node) {
|
||||
|
||||
// processCompositeLiteral marks fields as used if the struct is used
|
||||
// in a composite literal.
|
||||
func (c *Checker) processCompositeLiteral(pkg *loader.PackageInfo, node ast.Node) {
|
||||
func (c *Checker) processCompositeLiteral(pkg *lint.Pkg, node ast.Node) {
|
||||
// XXX how does this actually work? wouldn't it match t{}?
|
||||
if node, ok := node.(*ast.CompositeLit); ok {
|
||||
typ := pkg.TypeOf(node)
|
||||
typ := pkg.TypesInfo.TypeOf(node)
|
||||
if _, ok := typ.(*types.Named); ok {
|
||||
typ = typ.Underlying()
|
||||
}
|
||||
@ -702,7 +715,7 @@ func (c *Checker) processCompositeLiteral(pkg *loader.PackageInfo, node ast.Node
|
||||
|
||||
// processCgoExported marks functions as used if they're being
|
||||
// exported to cgo.
|
||||
func (c *Checker) processCgoExported(pkg *loader.PackageInfo, node ast.Node) {
|
||||
func (c *Checker) processCgoExported(pkg *lint.Pkg, node ast.Node) {
|
||||
if node, ok := node.(*ast.FuncDecl); ok {
|
||||
if node.Doc == nil {
|
||||
return
|
||||
@ -711,13 +724,13 @@ func (c *Checker) processCgoExported(pkg *loader.PackageInfo, node ast.Node) {
|
||||
if !strings.HasPrefix(cmt.Text, "//go:cgo_export_") {
|
||||
return
|
||||
}
|
||||
obj := pkg.ObjectOf(node.Name)
|
||||
obj := pkg.TypesInfo.ObjectOf(node.Name)
|
||||
c.graph.roots = append(c.graph.roots, c.graph.getNode(obj))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) processVariableDeclaration(pkg *loader.PackageInfo, node ast.Node) {
|
||||
func (c *Checker) processVariableDeclaration(pkg *lint.Pkg, node ast.Node) {
|
||||
if decl, ok := node.(*ast.GenDecl); ok {
|
||||
for _, spec := range decl.Specs {
|
||||
spec, ok := spec.(*ast.ValueSpec)
|
||||
@ -731,11 +744,11 @@ func (c *Checker) processVariableDeclaration(pkg *loader.PackageInfo, node ast.N
|
||||
value := spec.Values[i]
|
||||
fn := func(node ast.Node) bool {
|
||||
if node3, ok := node.(*ast.Ident); ok {
|
||||
obj := pkg.ObjectOf(node3)
|
||||
obj := pkg.TypesInfo.ObjectOf(node3)
|
||||
if _, ok := obj.(*types.PkgName); ok {
|
||||
return true
|
||||
}
|
||||
c.graph.markUsedBy(obj, pkg.ObjectOf(name))
|
||||
c.graph.markUsedBy(obj, pkg.TypesInfo.ObjectOf(name))
|
||||
}
|
||||
return true
|
||||
}
|
||||
@ -745,17 +758,17 @@ func (c *Checker) processVariableDeclaration(pkg *loader.PackageInfo, node ast.N
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) processArrayConstants(pkg *loader.PackageInfo, node ast.Node) {
|
||||
func (c *Checker) processArrayConstants(pkg *lint.Pkg, node ast.Node) {
|
||||
if decl, ok := node.(*ast.ArrayType); ok {
|
||||
ident, ok := decl.Len.(*ast.Ident)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
c.graph.markUsedBy(pkg.ObjectOf(ident), pkg.TypeOf(decl))
|
||||
c.graph.markUsedBy(pkg.TypesInfo.ObjectOf(ident), pkg.TypesInfo.TypeOf(decl))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) processKnownReflectMethodCallers(pkg *loader.PackageInfo, node ast.Node) {
|
||||
func (c *Checker) processKnownReflectMethodCallers(pkg *lint.Pkg, node ast.Node) {
|
||||
call, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return
|
||||
@ -764,12 +777,12 @@ func (c *Checker) processKnownReflectMethodCallers(pkg *loader.PackageInfo, node
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !IsType(pkg.TypeOf(sel.X), "*net/rpc.Server") {
|
||||
if !IsType(pkg.TypesInfo.TypeOf(sel.X), "*net/rpc.Server") {
|
||||
x, ok := sel.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
pkgname, ok := pkg.ObjectOf(x).(*types.PkgName)
|
||||
pkgname, ok := pkg.TypesInfo.ObjectOf(x).(*types.PkgName)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@ -791,14 +804,14 @@ func (c *Checker) processKnownReflectMethodCallers(pkg *loader.PackageInfo, node
|
||||
}
|
||||
arg = call.Args[1]
|
||||
}
|
||||
typ := pkg.TypeOf(arg)
|
||||
typ := pkg.TypesInfo.TypeOf(arg)
|
||||
ms := types.NewMethodSet(typ)
|
||||
for i := 0; i < ms.Len(); i++ {
|
||||
c.graph.markUsedBy(ms.At(i).Obj(), typ)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) processAST(pkg *loader.PackageInfo) {
|
||||
func (c *Checker) processAST(pkg *lint.Pkg) {
|
||||
fn := func(node ast.Node) bool {
|
||||
c.processConversion(pkg, node)
|
||||
c.processKnownReflectMethodCallers(pkg, node)
|
||||
@ -808,7 +821,7 @@ func (c *Checker) processAST(pkg *loader.PackageInfo) {
|
||||
c.processArrayConstants(pkg, node)
|
||||
return true
|
||||
}
|
||||
for _, file := range pkg.Files {
|
||||
for _, file := range pkg.Syntax {
|
||||
ast.Inspect(file, fn)
|
||||
}
|
||||
}
|
||||
@ -914,7 +927,7 @@ func (c *Checker) isRoot(obj types.Object) bool {
|
||||
return true
|
||||
}
|
||||
if obj.Exported() {
|
||||
f := c.lprog.Fset.Position(obj.Pos()).Filename
|
||||
f := c.prog.Fset().Position(obj.Pos()).Filename
|
||||
if strings.HasSuffix(f, "_test.go") {
|
||||
return strings.HasPrefix(obj.Name(), "Test") ||
|
||||
strings.HasPrefix(obj.Name(), "Benchmark") ||
|
||||
@ -939,6 +952,33 @@ func markNodesUsed(nodes map[*graphNode]struct{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// deduplicate merges objects based on their positions. This is done
|
||||
// to work around packages existing multiple times in go/packages.
|
||||
func (c *Checker) deduplicate() {
|
||||
m := map[token.Position]struct{ used, quiet bool }{}
|
||||
for _, node := range c.graph.nodes {
|
||||
obj, ok := node.obj.(types.Object)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
pos := c.prog.Fset().Position(obj.Pos())
|
||||
m[pos] = struct{ used, quiet bool }{
|
||||
m[pos].used || node.used,
|
||||
m[pos].quiet || node.quiet,
|
||||
}
|
||||
}
|
||||
|
||||
for _, node := range c.graph.nodes {
|
||||
obj, ok := node.obj.(types.Object)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
pos := c.prog.Fset().Position(obj.Pos())
|
||||
node.used = m[pos].used
|
||||
node.quiet = m[pos].quiet
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Checker) markNodesQuiet() {
|
||||
for _, node := range c.graph.nodes {
|
||||
if node.used {
|
||||
@ -1058,8 +1098,3 @@ func (c *Checker) printDebugGraph(w io.Writer) {
|
||||
}
|
||||
fmt.Fprintln(w, "}")
|
||||
}
|
||||
|
||||
func isGenerated(comment string) bool {
|
||||
return strings.Contains(comment, "Code generated by") ||
|
||||
strings.Contains(comment, "DO NOT EDIT")
|
||||
}
|
||||
|
Reference in New Issue
Block a user