support interfacer

This commit is contained in:
golangci 2018-05-07 09:48:43 +03:00
parent 364dd68ed8
commit c6dc47bcbd
33 changed files with 1899 additions and 35 deletions

27
Gopkg.lock generated
View File

@ -72,7 +72,7 @@
"unused",
"version"
]
revision = "38c5f4a0efc6bd0efc88bdd3890e5e64e698bdc2"
revision = "f557f368b7f3d00a54ed44eb57b9eca59e85cee1"
[[projects]]
branch = "master"
@ -136,6 +136,15 @@
revision = "7a3d63cfc6fbd9e46f2e551f9b8d1e9c69bffab1"
source = "github.com/golangci/errcheck"
[[projects]]
name = "github.com/kisielk/gotool"
packages = [
".",
"internal/load"
]
revision = "80517062f582ea3340cd4baf70e86d539ae7d84d"
version = "v1.0.0"
[[projects]]
name = "github.com/mattn/go-colorable"
packages = ["."]
@ -243,6 +252,20 @@
]
revision = "87723262609ca8fd55d449c027454c29cadefd68"
[[projects]]
branch = "master"
name = "mvdan.cc/interfacer"
packages = ["check"]
revision = "72c3fb5d5e5e0ca07e9a7c90bcd150b049920b3b"
source = "github.com/golangci/interfacer"
[[projects]]
branch = "master"
name = "mvdan.cc/lint"
packages = ["."]
revision = "8ff1696d5934157cea033c4b97cf5ea23fdb7a32"
source = "github.com/golangci/lint"
[[projects]]
branch = "master"
name = "sourcegraph.com/sourcegraph/go-diff"
@ -258,6 +281,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "aa0c6cd4a6cd5957ffdeeb0f06753a92f33f5393045845e45bc63a10ead24a85"
inputs-digest = "6e4437a7adb255525e8d1cfda07716617c1f28acaa721ac4f910f52f49ecea2f"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -55,6 +55,16 @@
branch = "master"
source = "github.com/golangci/dupl"
[[constraint]]
name = "mvdan.cc/interfacer"
branch = "master"
source = "github.com/golangci/interfacer"
[[override]]
name = "mvdan.cc/lint"
branch = "master"
source = "github.com/golangci/lint"
[prune]
go-tests = true
unused-packages = true

View File

@ -10,6 +10,8 @@ import (
"time"
"github.com/fatih/color"
"github.com/golangci/go-tools/ssa"
"github.com/golangci/go-tools/ssa/ssautil"
"github.com/golangci/golangci-lint/pkg"
"github.com/golangci/golangci-lint/pkg/config"
"github.com/golangci/golangci-lint/pkg/fsutils"
@ -84,6 +86,17 @@ func isFullImportNeeded(linters []pkg.Linter) bool {
return false
}
func isSSAReprNeeded(linters []pkg.Linter) bool {
for _, linter := range linters {
lc := pkg.GetLinterConfig(linter.Name())
if lc.NeedsSSARepr {
return true
}
}
return false
}
func loadWholeAppIfNeeded(ctx context.Context, linters []pkg.Linter, cfg *config.Run, paths *fsutils.ProjectPaths) (*loader.Program, *loader.Config, error) {
if !isFullImportNeeded(linters) {
return nil, nil, nil
@ -117,6 +130,17 @@ func loadWholeAppIfNeeded(ctx context.Context, linters []pkg.Linter, cfg *config
return prog, loadcfg, nil
}
func buildSSAProgram(ctx context.Context, lprog *loader.Program) *ssa.Program {
startedAt := time.Now()
defer func() {
analytics.Log(ctx).Infof("SSA repr building took %s", time.Since(startedAt))
}()
ssaProg := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
ssaProg.Build()
return ssaProg
}
func buildLintCtx(ctx context.Context, linters []pkg.Linter, cfg *config.Config) (*golinters.Context, error) {
args := cfg.Run.Args
if len(args) == 0 {
@ -133,10 +157,16 @@ func buildLintCtx(ctx context.Context, linters []pkg.Linter, cfg *config.Config)
return nil, err
}
var ssaProg *ssa.Program
if prog != nil && isSSAReprNeeded(linters) {
ssaProg = buildSSAProgram(ctx, prog)
}
return &golinters.Context{
Paths: paths,
Cfg: cfg,
Program: prog,
SSAProgram: ssaProg,
LoaderConfig: loaderConfig,
}, nil
}

View File

@ -15,6 +15,7 @@ type LinterConfig struct {
Linter Linter
EnabledByDefault bool
DoesFullImport bool
NeedsSSARepr bool
}
var nameToLC map[string]LinterConfig
@ -36,40 +37,43 @@ func GetLinterConfig(name string) *LinterConfig {
return &lc
}
func enabledByDefault(linter Linter, desc string, doesFullImport bool) LinterConfig {
func enabledByDefault(linter Linter, desc string, doesFullImport, needsSSARepr bool) LinterConfig {
return LinterConfig{
EnabledByDefault: true,
Linter: linter,
Desc: desc,
DoesFullImport: doesFullImport,
NeedsSSARepr: needsSSARepr,
}
}
func disabledByDefault(linter Linter, desc string, doesFullImport bool) LinterConfig {
func disabledByDefault(linter Linter, desc string, doesFullImport, needsSSARepr bool) LinterConfig {
return LinterConfig{
EnabledByDefault: false,
Linter: linter,
Desc: desc,
DoesFullImport: doesFullImport,
NeedsSSARepr: needsSSARepr,
}
}
func GetAllSupportedLinterConfigs() []LinterConfig {
return []LinterConfig{
enabledByDefault(golinters.Govet{}, "Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string", false),
enabledByDefault(golinters.Errcheck{}, "Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases", true),
enabledByDefault(golinters.Golint{}, "Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes", false),
enabledByDefault(golinters.Deadcode{}, "Finds unused code", true),
enabledByDefault(golinters.Gocyclo{}, "Computes and checks the cyclomatic complexity of functions", false),
enabledByDefault(golinters.Structcheck{}, "Finds unused struct fields", true),
enabledByDefault(golinters.Varcheck{}, "Finds unused global variables and constants", true),
enabledByDefault(golinters.Megacheck{}, "Megacheck: 3 sub-linters in one: staticcheck, gosimple and unused", true),
enabledByDefault(golinters.Dupl{}, "Tool for code clone detection", false),
enabledByDefault(golinters.Ineffassign{}, "Detects when assignments to existing variables are not used", false),
enabledByDefault(golinters.Govet{}, "Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string", false, false),
enabledByDefault(golinters.Errcheck{}, "Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases", true, false),
enabledByDefault(golinters.Golint{}, "Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes", false, false),
enabledByDefault(golinters.Deadcode{}, "Finds unused code", true, false),
enabledByDefault(golinters.Gocyclo{}, "Computes and checks the cyclomatic complexity of functions", false, false),
enabledByDefault(golinters.Structcheck{}, "Finds unused struct fields", true, false),
enabledByDefault(golinters.Varcheck{}, "Finds unused global variables and constants", true, false),
enabledByDefault(golinters.Megacheck{}, "Megacheck: 3 sub-linters in one: staticcheck, gosimple and unused", true, true),
enabledByDefault(golinters.Dupl{}, "Tool for code clone detection", false, false),
enabledByDefault(golinters.Ineffassign{}, "Detects when assignments to existing variables are not used", false, false),
enabledByDefault(golinters.Interfacer{}, "Linter that suggests narrower interface types", true, true),
disabledByDefault(golinters.Gofmt{}, "Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification", false),
disabledByDefault(golinters.Gofmt{UseGoimports: true}, "Goimports does everything that gofmt does. Additionally it checks unused imports", false),
disabledByDefault(golinters.Maligned{}, "Tool to detect Go structs that would take less memory if their fields were sorted", true),
disabledByDefault(golinters.Gofmt{}, "Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification", false, false),
disabledByDefault(golinters.Gofmt{UseGoimports: true}, "Goimports does everything that gofmt does. Additionally it checks unused imports", false, false),
disabledByDefault(golinters.Maligned{}, "Tool to detect Go structs that would take less memory if their fields were sorted", true, false),
}
}

View File

@ -1,6 +1,7 @@
package golinters
import (
"github.com/golangci/go-tools/ssa"
"github.com/golangci/golangci-lint/pkg/config"
"github.com/golangci/golangci-lint/pkg/fsutils"
"golang.org/x/tools/go/loader"
@ -10,6 +11,7 @@ type Context struct {
Paths *fsutils.ProjectPaths
Cfg *config.Config
Program *loader.Program
SSAProgram *ssa.Program
LoaderConfig *loader.Config
}

View File

@ -0,0 +1,39 @@
package golinters
import (
"context"
"mvdan.cc/interfacer/check"
"github.com/golangci/golangci-lint/pkg/result"
)
type Interfacer struct{}
func (Interfacer) Name() string {
return "interfacer"
}
func (lint Interfacer) Run(ctx context.Context, lintCtx *Context) (*result.Result, error) {
c := new(check.Checker)
c.Program(lintCtx.Program)
c.ProgramSSA(lintCtx.SSAProgram)
issues, err := c.Check()
if err != nil {
return nil, err
}
res := &result.Result{}
for _, i := range issues {
pos := lintCtx.SSAProgram.Fset.Position(i.Pos())
res.Issues = append(res.Issues, result.Issue{
File: pos.Filename,
LineNumber: pos.Line,
Text: i.Message(),
FromLinter: lint.Name(),
})
}
return res, nil
}

View File

@ -15,7 +15,7 @@ func (Megacheck) Name() string {
func (m Megacheck) Run(ctx context.Context, lintCtx *Context) (*result.Result, error) {
c := lintCtx.RunCfg().Megacheck
issues := megacheckAPI.Run(lintCtx.Program, lintCtx.LoaderConfig, c.EnableStaticcheck, c.EnableGosimple, c.EnableUnused)
issues := megacheckAPI.Run(lintCtx.Program, lintCtx.LoaderConfig, lintCtx.SSAProgram, c.EnableStaticcheck, c.EnableGosimple, c.EnableUnused)
res := &result.Result{}
for _, i := range issues {

7
pkg/golinters/testdata/interfacer.go vendored Normal file
View File

@ -0,0 +1,7 @@
package testdata
import "io"
func InterfacerCheck(f io.ReadCloser) { // ERROR "XXX"
f.Close()
}

View File

@ -5,12 +5,13 @@ import (
"github.com/golangci/go-tools/lint"
"github.com/golangci/go-tools/lint/lintutil"
"github.com/golangci/go-tools/simple"
"github.com/golangci/go-tools/ssa"
"github.com/golangci/go-tools/staticcheck"
"github.com/golangci/go-tools/unused"
"golang.org/x/tools/go/loader"
)
func Run(program *loader.Program, conf *loader.Config, enableStaticcheck, enableGosimple, enableUnused bool) []lint.Problem {
func Run(program *loader.Program, conf *loader.Config, ssaProg *ssa.Program, enableStaticcheck, enableGosimple, enableUnused bool) []lint.Problem {
var flags struct {
staticcheck struct {
enabled bool
@ -120,5 +121,5 @@ func Run(program *loader.Program, conf *loader.Config, enableStaticcheck, enable
}
return lintutil.ProcessFlagSet(checkers, fs, program, conf)
return lintutil.ProcessFlagSet(checkers, fs, program, conf, ssaProg)
}

View File

@ -23,10 +23,10 @@ import (
"sync"
"unicode"
"github.com/golangci/go-tools/ssa"
"github.com/golangci/go-tools/ssa/ssautil"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/loader"
"github.com/golangci/go-tools/ssa"
"github.com/golangci/go-tools/ssa/ssautil"
)
type Job struct {
@ -235,9 +235,7 @@ 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)
ssaprog.Build()
func (l *Linter) Lint(lprog *loader.Program, conf *loader.Config, ssaprog *ssa.Program) []Problem {
pkgMap := map[*ssa.Package]*Pkg{}
var pkgs []*Pkg
for _, pkginfo := range lprog.InitialPackages() {
@ -836,9 +834,6 @@ func (v *fnVisitor) Visit(node ast.Node) ast.Visitor {
switch node := node.(type) {
case *ast.FuncDecl:
var ssafn *ssa.Function
if v.pkg == nil || v.pkg.Prog == nil {
return nil // partially loaded
}
ssafn = v.pkg.Prog.FuncValue(v.pkg.Info.ObjectOf(node.Name).(*types.Func))
v.m[node] = ssafn
if ssafn == nil {

View File

@ -23,6 +23,7 @@ import (
"github.com/golangci/go-tools/lint"
"github.com/golangci/go-tools/version"
"github.com/golangci/go-tools/ssa"
"golang.org/x/tools/go/loader"
)
@ -182,7 +183,7 @@ type CheckerConfig struct {
ExitNonZero bool
}
func ProcessFlagSet(confs []CheckerConfig, fs *flag.FlagSet, lprog *loader.Program, conf *loader.Config) []lint.Problem {
func ProcessFlagSet(confs []CheckerConfig, fs *flag.FlagSet, lprog *loader.Program, conf *loader.Config, ssaProg *ssa.Program) []lint.Problem {
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)
@ -199,7 +200,7 @@ func ProcessFlagSet(confs []CheckerConfig, fs *flag.FlagSet, lprog *loader.Progr
for _, conf := range confs {
cs = append(cs, conf.Checker)
}
pss, err := Lint(cs, lprog, conf, &Options{
pss, err := Lint(cs, lprog, conf, ssaProg, &Options{
Tags: strings.Fields(tags),
LintTests: tests,
Ignores: ignore,
@ -226,7 +227,7 @@ type Options struct {
ReturnIgnored bool
}
func Lint(cs []lint.Checker, lprog *loader.Program, conf *loader.Config, opt *Options) ([][]lint.Problem, error) {
func Lint(cs []lint.Checker, lprog *loader.Program, conf *loader.Config, ssaProg *ssa.Program, opt *Options) ([][]lint.Problem, error) {
if opt == nil {
opt = &Options{}
}
@ -244,7 +245,7 @@ func Lint(cs []lint.Checker, lprog *loader.Program, conf *loader.Config, opt *Op
version: opt.GoVersion,
returnIgnored: opt.ReturnIgnored,
}
problems = append(problems, runner.lint(lprog, conf))
problems = append(problems, runner.lint(lprog, conf, ssaProg))
}
return problems, nil
}
@ -278,15 +279,15 @@ func ProcessArgs(name string, cs []CheckerConfig, args []string) {
flags := FlagSet(name)
flags.Parse(args)
ProcessFlagSet(cs, flags, nil, nil)
ProcessFlagSet(cs, flags, nil, nil, nil)
}
func (runner *runner) lint(lprog *loader.Program, conf *loader.Config) []lint.Problem {
func (runner *runner) lint(lprog *loader.Program, conf *loader.Config, ssaProg *ssa.Program) []lint.Problem {
l := &lint.Linter{
Checker: runner.checker,
Ignores: runner.ignores,
GoVersion: runner.version,
ReturnIgnored: runner.returnIgnored,
}
return l.Lint(lprog, conf)
return l.Lint(lprog, conf, ssaProg)
}

23
vendor/github.com/kisielk/gotool/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,23 @@
sudo: false
language: go
go:
- 1.2
- 1.3
- 1.4
- 1.5
- 1.6
- 1.7
- 1.8
- 1.9
- master
matrix:
allow_failures:
- go: master
fast_finish: true
install:
- # Skip.
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d .)
- go tool vet .
- go test -v -race ./...

32
vendor/github.com/kisielk/gotool/LEGAL generated vendored Normal file
View File

@ -0,0 +1,32 @@
All the files in this distribution are covered under either the MIT
license (see the file LICENSE) except some files mentioned below.
match.go, match_test.go:
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

20
vendor/github.com/kisielk/gotool/LICENSE generated vendored Normal file
View File

@ -0,0 +1,20 @@
Copyright (c) 2013 Kamil Kisiel <kamil@kamilkisiel.net>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

6
vendor/github.com/kisielk/gotool/README.md generated vendored Normal file
View File

@ -0,0 +1,6 @@
gotool
======
[![GoDoc](https://godoc.org/github.com/kisielk/gotool?status.svg)](https://godoc.org/github.com/kisielk/gotool)
[![Build Status](https://travis-ci.org/kisielk/gotool.svg?branch=master)](https://travis-ci.org/kisielk/gotool)
Package gotool contains utility functions used to implement the standard "cmd/go" tool, provided as a convenience to developers who want to write tools with similar semantics.

1
vendor/github.com/kisielk/gotool/go.mod generated vendored Normal file
View File

@ -0,0 +1 @@
module "github.com/kisielk/gotool"

15
vendor/github.com/kisielk/gotool/go13.go generated vendored Normal file
View File

@ -0,0 +1,15 @@
// +build !go1.4
package gotool
import (
"go/build"
"path/filepath"
"runtime"
)
var gorootSrc = filepath.Join(runtime.GOROOT(), "src", "pkg")
func shouldIgnoreImport(p *build.Package) bool {
return true
}

15
vendor/github.com/kisielk/gotool/go14-15.go generated vendored Normal file
View File

@ -0,0 +1,15 @@
// +build go1.4,!go1.6
package gotool
import (
"go/build"
"path/filepath"
"runtime"
)
var gorootSrc = filepath.Join(runtime.GOROOT(), "src")
func shouldIgnoreImport(p *build.Package) bool {
return true
}

15
vendor/github.com/kisielk/gotool/go16-18.go generated vendored Normal file
View File

@ -0,0 +1,15 @@
// +build go1.6,!go1.9
package gotool
import (
"go/build"
"path/filepath"
"runtime"
)
var gorootSrc = filepath.Join(runtime.GOROOT(), "src")
func shouldIgnoreImport(p *build.Package) bool {
return p == nil || len(p.InvalidGoFiles) == 0
}

27
vendor/github.com/kisielk/gotool/internal/load/path.go generated vendored Normal file
View File

@ -0,0 +1,27 @@
// Copyright 2017 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.
// +build go1.9
package load
import (
"strings"
)
// hasPathPrefix reports whether the path s begins with the
// elements in prefix.
func hasPathPrefix(s, prefix string) bool {
switch {
default:
return false
case len(s) == len(prefix):
return s == prefix
case len(s) > len(prefix):
if prefix != "" && prefix[len(prefix)-1] == '/' {
return strings.HasPrefix(s, prefix)
}
return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
}
}

25
vendor/github.com/kisielk/gotool/internal/load/pkg.go generated vendored Normal file
View File

@ -0,0 +1,25 @@
// Copyright 2011 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.
// +build go1.9
// Package load loads packages.
package load
import (
"strings"
)
// isStandardImportPath reports whether $GOROOT/src/path should be considered
// part of the standard distribution. For historical reasons we allow people to add
// their own code to $GOROOT instead of using $GOPATH, but we assume that
// code will start with a domain name (dot in the first element).
func isStandardImportPath(path string) bool {
i := strings.Index(path, "/")
if i < 0 {
i = len(path)
}
elem := path[:i]
return !strings.Contains(elem, ".")
}

View File

@ -0,0 +1,354 @@
// Copyright 2017 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.
// +build go1.9
package load
import (
"fmt"
"go/build"
"log"
"os"
"path"
"path/filepath"
"regexp"
"strings"
)
// Context specifies values for operation of ImportPaths that would
// otherwise come from cmd/go/internal/cfg package.
//
// This is a construct added for gotool purposes and doesn't have
// an equivalent upstream in cmd/go.
type Context struct {
// BuildContext is the build context to use.
BuildContext build.Context
// GOROOTsrc is the location of the src directory in GOROOT.
// At this time, it's used only in MatchPackages to skip
// GOOROOT/src entry from BuildContext.SrcDirs output.
GOROOTsrc string
}
// allPackages returns all the packages that can be found
// under the $GOPATH directories and $GOROOT matching pattern.
// The pattern is either "all" (all packages), "std" (standard packages),
// "cmd" (standard commands), or a path including "...".
func (c *Context) allPackages(pattern string) []string {
pkgs := c.MatchPackages(pattern)
if len(pkgs) == 0 {
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
}
return pkgs
}
// allPackagesInFS is like allPackages but is passed a pattern
// beginning ./ or ../, meaning it should scan the tree rooted
// at the given directory. There are ... in the pattern too.
func (c *Context) allPackagesInFS(pattern string) []string {
pkgs := c.MatchPackagesInFS(pattern)
if len(pkgs) == 0 {
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
}
return pkgs
}
// MatchPackages returns a list of package paths matching pattern
// (see go help packages for pattern syntax).
func (c *Context) MatchPackages(pattern string) []string {
match := func(string) bool { return true }
treeCanMatch := func(string) bool { return true }
if !IsMetaPackage(pattern) {
match = matchPattern(pattern)
treeCanMatch = treeCanMatchPattern(pattern)
}
have := map[string]bool{
"builtin": true, // ignore pseudo-package that exists only for documentation
}
if !c.BuildContext.CgoEnabled {
have["runtime/cgo"] = true // ignore during walk
}
var pkgs []string
for _, src := range c.BuildContext.SrcDirs() {
if (pattern == "std" || pattern == "cmd") && src != c.GOROOTsrc {
continue
}
src = filepath.Clean(src) + string(filepath.Separator)
root := src
if pattern == "cmd" {
root += "cmd" + string(filepath.Separator)
}
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if err != nil || path == src {
return nil
}
want := true
// Avoid .foo, _foo, and testdata directory trees.
_, elem := filepath.Split(path)
if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
want = false
}
name := filepath.ToSlash(path[len(src):])
if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") {
// The name "std" is only the standard library.
// If the name is cmd, it's the root of the command tree.
want = false
}
if !treeCanMatch(name) {
want = false
}
if !fi.IsDir() {
if fi.Mode()&os.ModeSymlink != 0 && want {
if target, err := os.Stat(path); err == nil && target.IsDir() {
fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
}
}
return nil
}
if !want {
return filepath.SkipDir
}
if have[name] {
return nil
}
have[name] = true
if !match(name) {
return nil
}
pkg, err := c.BuildContext.ImportDir(path, 0)
if err != nil {
if _, noGo := err.(*build.NoGoError); noGo {
return nil
}
}
// If we are expanding "cmd", skip main
// packages under cmd/vendor. At least as of
// March, 2017, there is one there for the
// vendored pprof tool.
if pattern == "cmd" && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" {
return nil
}
pkgs = append(pkgs, name)
return nil
})
}
return pkgs
}
// MatchPackagesInFS returns a list of package paths matching pattern,
// which must begin with ./ or ../
// (see go help packages for pattern syntax).
func (c *Context) MatchPackagesInFS(pattern string) []string {
// Find directory to begin the scan.
// Could be smarter but this one optimization
// is enough for now, since ... is usually at the
// end of a path.
i := strings.Index(pattern, "...")
dir, _ := path.Split(pattern[:i])
// pattern begins with ./ or ../.
// path.Clean will discard the ./ but not the ../.
// We need to preserve the ./ for pattern matching
// and in the returned import paths.
prefix := ""
if strings.HasPrefix(pattern, "./") {
prefix = "./"
}
match := matchPattern(pattern)
var pkgs []string
filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
if err != nil || !fi.IsDir() {
return nil
}
if path == dir {
// filepath.Walk starts at dir and recurses. For the recursive case,
// the path is the result of filepath.Join, which calls filepath.Clean.
// The initial case is not Cleaned, though, so we do this explicitly.
//
// This converts a path like "./io/" to "io". Without this step, running
// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
// package, because prepending the prefix "./" to the unclean path would
// result in "././io", and match("././io") returns false.
path = filepath.Clean(path)
}
// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
_, elem := filepath.Split(path)
dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
return filepath.SkipDir
}
name := prefix + filepath.ToSlash(path)
if !match(name) {
return nil
}
// We keep the directory if we can import it, or if we can't import it
// due to invalid Go source files. This means that directories containing
// parse errors will be built (and fail) instead of being silently skipped
// as not matching the pattern. Go 1.5 and earlier skipped, but that
// behavior means people miss serious mistakes.
// See golang.org/issue/11407.
if p, err := c.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) {
if _, noGo := err.(*build.NoGoError); !noGo {
log.Print(err)
}
return nil
}
pkgs = append(pkgs, name)
return nil
})
return pkgs
}
// treeCanMatchPattern(pattern)(name) reports whether
// name or children of name can possibly match pattern.
// Pattern is the same limited glob accepted by matchPattern.
func treeCanMatchPattern(pattern string) func(name string) bool {
wildCard := false
if i := strings.Index(pattern, "..."); i >= 0 {
wildCard = true
pattern = pattern[:i]
}
return func(name string) bool {
return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
wildCard && strings.HasPrefix(name, pattern)
}
}
// matchPattern(pattern)(name) reports whether
// name matches pattern. Pattern is a limited glob
// pattern in which '...' means 'any string' and there
// is no other special syntax.
// Unfortunately, there are two special cases. Quoting "go help packages":
//
// First, /... at the end of the pattern can match an empty string,
// so that net/... matches both net and packages in its subdirectories, like net/http.
// Second, any slash-separted pattern element containing a wildcard never
// participates in a match of the "vendor" element in the path of a vendored
// package, so that ./... does not match packages in subdirectories of
// ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
// Note, however, that a directory named vendor that itself contains code
// is not a vendored package: cmd/vendor would be a command named vendor,
// and the pattern cmd/... matches it.
func matchPattern(pattern string) func(name string) bool {
// Convert pattern to regular expression.
// The strategy for the trailing /... is to nest it in an explicit ? expression.
// The strategy for the vendor exclusion is to change the unmatchable
// vendor strings to a disallowed code point (vendorChar) and to use
// "(anything but that codepoint)*" as the implementation of the ... wildcard.
// This is a bit complicated but the obvious alternative,
// namely a hand-written search like in most shell glob matchers,
// is too easy to make accidentally exponential.
// Using package regexp guarantees linear-time matching.
const vendorChar = "\x00"
if strings.Contains(pattern, vendorChar) {
return func(name string) bool { return false }
}
re := regexp.QuoteMeta(pattern)
re = replaceVendor(re, vendorChar)
switch {
case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
case re == vendorChar+`/\.\.\.`:
re = `(/vendor|/` + vendorChar + `/\.\.\.)`
case strings.HasSuffix(re, `/\.\.\.`):
re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
}
re = strings.Replace(re, `\.\.\.`, `[^`+vendorChar+`]*`, -1)
reg := regexp.MustCompile(`^` + re + `$`)
return func(name string) bool {
if strings.Contains(name, vendorChar) {
return false
}
return reg.MatchString(replaceVendor(name, vendorChar))
}
}
// replaceVendor returns the result of replacing
// non-trailing vendor path elements in x with repl.
func replaceVendor(x, repl string) string {
if !strings.Contains(x, "vendor") {
return x
}
elem := strings.Split(x, "/")
for i := 0; i < len(elem)-1; i++ {
if elem[i] == "vendor" {
elem[i] = repl
}
}
return strings.Join(elem, "/")
}
// ImportPaths returns the import paths to use for the given command line.
func (c *Context) ImportPaths(args []string) []string {
args = c.ImportPathsNoDotExpansion(args)
var out []string
for _, a := range args {
if strings.Contains(a, "...") {
if build.IsLocalImport(a) {
out = append(out, c.allPackagesInFS(a)...)
} else {
out = append(out, c.allPackages(a)...)
}
continue
}
out = append(out, a)
}
return out
}
// ImportPathsNoDotExpansion returns the import paths to use for the given
// command line, but it does no ... expansion.
func (c *Context) ImportPathsNoDotExpansion(args []string) []string {
if len(args) == 0 {
return []string{"."}
}
var out []string
for _, a := range args {
// Arguments are supposed to be import paths, but
// as a courtesy to Windows developers, rewrite \ to /
// in command-line arguments. Handles .\... and so on.
if filepath.Separator == '\\' {
a = strings.Replace(a, `\`, `/`, -1)
}
// Put argument in canonical form, but preserve leading ./.
if strings.HasPrefix(a, "./") {
a = "./" + path.Clean(a)
if a == "./." {
a = "."
}
} else {
a = path.Clean(a)
}
if IsMetaPackage(a) {
out = append(out, c.allPackages(a)...)
continue
}
out = append(out, a)
}
return out
}
// IsMetaPackage checks if name is a reserved package name that expands to multiple packages.
func IsMetaPackage(name string) bool {
return name == "std" || name == "cmd" || name == "all"
}

56
vendor/github.com/kisielk/gotool/match.go generated vendored Normal file
View File

@ -0,0 +1,56 @@
// Copyright (c) 2009 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// +build go1.9
package gotool
import (
"path/filepath"
"github.com/kisielk/gotool/internal/load"
)
// importPaths returns the import paths to use for the given command line.
func (c *Context) importPaths(args []string) []string {
lctx := load.Context{
BuildContext: c.BuildContext,
GOROOTsrc: c.joinPath(c.BuildContext.GOROOT, "src"),
}
return lctx.ImportPaths(args)
}
// joinPath calls c.BuildContext.JoinPath (if not nil) or else filepath.Join.
//
// It's a copy of the unexported build.Context.joinPath helper.
func (c *Context) joinPath(elem ...string) string {
if f := c.BuildContext.JoinPath; f != nil {
return f(elem...)
}
return filepath.Join(elem...)
}

317
vendor/github.com/kisielk/gotool/match18.go generated vendored Normal file
View File

@ -0,0 +1,317 @@
// Copyright (c) 2009 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// +build !go1.9
package gotool
import (
"fmt"
"go/build"
"log"
"os"
"path"
"path/filepath"
"regexp"
"strings"
)
// This file contains code from the Go distribution.
// matchPattern(pattern)(name) reports whether
// name matches pattern. Pattern is a limited glob
// pattern in which '...' means 'any string' and there
// is no other special syntax.
func matchPattern(pattern string) func(name string) bool {
re := regexp.QuoteMeta(pattern)
re = strings.Replace(re, `\.\.\.`, `.*`, -1)
// Special case: foo/... matches foo too.
if strings.HasSuffix(re, `/.*`) {
re = re[:len(re)-len(`/.*`)] + `(/.*)?`
}
reg := regexp.MustCompile(`^` + re + `$`)
return reg.MatchString
}
// matchPackages returns a list of package paths matching pattern
// (see go help packages for pattern syntax).
func (c *Context) matchPackages(pattern string) []string {
match := func(string) bool { return true }
treeCanMatch := func(string) bool { return true }
if !isMetaPackage(pattern) {
match = matchPattern(pattern)
treeCanMatch = treeCanMatchPattern(pattern)
}
have := map[string]bool{
"builtin": true, // ignore pseudo-package that exists only for documentation
}
if !c.BuildContext.CgoEnabled {
have["runtime/cgo"] = true // ignore during walk
}
var pkgs []string
for _, src := range c.BuildContext.SrcDirs() {
if (pattern == "std" || pattern == "cmd") && src != gorootSrc {
continue
}
src = filepath.Clean(src) + string(filepath.Separator)
root := src
if pattern == "cmd" {
root += "cmd" + string(filepath.Separator)
}
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if err != nil || !fi.IsDir() || path == src {
return nil
}
// Avoid .foo, _foo, and testdata directory trees.
_, elem := filepath.Split(path)
if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
return filepath.SkipDir
}
name := filepath.ToSlash(path[len(src):])
if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") {
// The name "std" is only the standard library.
// If the name is cmd, it's the root of the command tree.
return filepath.SkipDir
}
if !treeCanMatch(name) {
return filepath.SkipDir
}
if have[name] {
return nil
}
have[name] = true
if !match(name) {
return nil
}
_, err = c.BuildContext.ImportDir(path, 0)
if err != nil {
if _, noGo := err.(*build.NoGoError); noGo {
return nil
}
}
pkgs = append(pkgs, name)
return nil
})
}
return pkgs
}
// importPathsNoDotExpansion returns the import paths to use for the given
// command line, but it does no ... expansion.
func (c *Context) importPathsNoDotExpansion(args []string) []string {
if len(args) == 0 {
return []string{"."}
}
var out []string
for _, a := range args {
// Arguments are supposed to be import paths, but
// as a courtesy to Windows developers, rewrite \ to /
// in command-line arguments. Handles .\... and so on.
if filepath.Separator == '\\' {
a = strings.Replace(a, `\`, `/`, -1)
}
// Put argument in canonical form, but preserve leading ./.
if strings.HasPrefix(a, "./") {
a = "./" + path.Clean(a)
if a == "./." {
a = "."
}
} else {
a = path.Clean(a)
}
if isMetaPackage(a) {
out = append(out, c.allPackages(a)...)
continue
}
out = append(out, a)
}
return out
}
// importPaths returns the import paths to use for the given command line.
func (c *Context) importPaths(args []string) []string {
args = c.importPathsNoDotExpansion(args)
var out []string
for _, a := range args {
if strings.Contains(a, "...") {
if build.IsLocalImport(a) {
out = append(out, c.allPackagesInFS(a)...)
} else {
out = append(out, c.allPackages(a)...)
}
continue
}
out = append(out, a)
}
return out
}
// allPackages returns all the packages that can be found
// under the $GOPATH directories and $GOROOT matching pattern.
// The pattern is either "all" (all packages), "std" (standard packages),
// "cmd" (standard commands), or a path including "...".
func (c *Context) allPackages(pattern string) []string {
pkgs := c.matchPackages(pattern)
if len(pkgs) == 0 {
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
}
return pkgs
}
// allPackagesInFS is like allPackages but is passed a pattern
// beginning ./ or ../, meaning it should scan the tree rooted
// at the given directory. There are ... in the pattern too.
func (c *Context) allPackagesInFS(pattern string) []string {
pkgs := c.matchPackagesInFS(pattern)
if len(pkgs) == 0 {
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
}
return pkgs
}
// matchPackagesInFS returns a list of package paths matching pattern,
// which must begin with ./ or ../
// (see go help packages for pattern syntax).
func (c *Context) matchPackagesInFS(pattern string) []string {
// Find directory to begin the scan.
// Could be smarter but this one optimization
// is enough for now, since ... is usually at the
// end of a path.
i := strings.Index(pattern, "...")
dir, _ := path.Split(pattern[:i])
// pattern begins with ./ or ../.
// path.Clean will discard the ./ but not the ../.
// We need to preserve the ./ for pattern matching
// and in the returned import paths.
prefix := ""
if strings.HasPrefix(pattern, "./") {
prefix = "./"
}
match := matchPattern(pattern)
var pkgs []string
filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
if err != nil || !fi.IsDir() {
return nil
}
if path == dir {
// filepath.Walk starts at dir and recurses. For the recursive case,
// the path is the result of filepath.Join, which calls filepath.Clean.
// The initial case is not Cleaned, though, so we do this explicitly.
//
// This converts a path like "./io/" to "io". Without this step, running
// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
// package, because prepending the prefix "./" to the unclean path would
// result in "././io", and match("././io") returns false.
path = filepath.Clean(path)
}
// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
_, elem := filepath.Split(path)
dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
return filepath.SkipDir
}
name := prefix + filepath.ToSlash(path)
if !match(name) {
return nil
}
// We keep the directory if we can import it, or if we can't import it
// due to invalid Go source files. This means that directories containing
// parse errors will be built (and fail) instead of being silently skipped
// as not matching the pattern. Go 1.5 and earlier skipped, but that
// behavior means people miss serious mistakes.
// See golang.org/issue/11407.
if p, err := c.BuildContext.ImportDir(path, 0); err != nil && shouldIgnoreImport(p) {
if _, noGo := err.(*build.NoGoError); !noGo {
log.Print(err)
}
return nil
}
pkgs = append(pkgs, name)
return nil
})
return pkgs
}
// isMetaPackage checks if name is a reserved package name that expands to multiple packages.
func isMetaPackage(name string) bool {
return name == "std" || name == "cmd" || name == "all"
}
// isStandardImportPath reports whether $GOROOT/src/path should be considered
// part of the standard distribution. For historical reasons we allow people to add
// their own code to $GOROOT instead of using $GOPATH, but we assume that
// code will start with a domain name (dot in the first element).
func isStandardImportPath(path string) bool {
i := strings.Index(path, "/")
if i < 0 {
i = len(path)
}
elem := path[:i]
return !strings.Contains(elem, ".")
}
// hasPathPrefix reports whether the path s begins with the
// elements in prefix.
func hasPathPrefix(s, prefix string) bool {
switch {
default:
return false
case len(s) == len(prefix):
return s == prefix
case len(s) > len(prefix):
if prefix != "" && prefix[len(prefix)-1] == '/' {
return strings.HasPrefix(s, prefix)
}
return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
}
}
// treeCanMatchPattern(pattern)(name) reports whether
// name or children of name can possibly match pattern.
// Pattern is the same limited glob accepted by matchPattern.
func treeCanMatchPattern(pattern string) func(name string) bool {
wildCard := false
if i := strings.Index(pattern, "..."); i >= 0 {
wildCard = true
pattern = pattern[:i]
}
return func(name string) bool {
return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
wildCard && strings.HasPrefix(name, pattern)
}
}

48
vendor/github.com/kisielk/gotool/tool.go generated vendored Normal file
View File

@ -0,0 +1,48 @@
// Package gotool contains utility functions used to implement the standard
// "cmd/go" tool, provided as a convenience to developers who want to write
// tools with similar semantics.
package gotool
import "go/build"
// Export functions here to make it easier to keep the implementations up to date with upstream.
// DefaultContext is the default context that uses build.Default.
var DefaultContext = Context{
BuildContext: build.Default,
}
// A Context specifies the supporting context.
type Context struct {
// BuildContext is the build.Context that is used when computing import paths.
BuildContext build.Context
}
// ImportPaths returns the import paths to use for the given command line.
//
// The path "all" is expanded to all packages in $GOPATH and $GOROOT.
// The path "std" is expanded to all packages in the Go standard library.
// The path "cmd" is expanded to all Go standard commands.
// The string "..." is treated as a wildcard within a path.
// When matching recursively, directories are ignored if they are prefixed with
// a dot or an underscore (such as ".foo" or "_foo"), or are named "testdata".
// Relative import paths are not converted to full import paths.
// If args is empty, a single element "." is returned.
func (c *Context) ImportPaths(args []string) []string {
return c.importPaths(args)
}
// ImportPaths returns the import paths to use for the given command line
// using default context.
//
// The path "all" is expanded to all packages in $GOPATH and $GOROOT.
// The path "std" is expanded to all packages in the Go standard library.
// The path "cmd" is expanded to all Go standard commands.
// The string "..." is treated as a wildcard within a path.
// When matching recursively, directories are ignored if they are prefixed with
// a dot or an underscore (such as ".foo" or "_foo"), or are named "testdata".
// Relative import paths are not converted to full import paths.
// If args is empty, a single element "." is returned.
func ImportPaths(args []string) []string {
return DefaultContext.importPaths(args)
}

27
vendor/mvdan.cc/interfacer/LICENSE vendored Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2015, Daniel Martí. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,50 @@
// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package check
import (
"go/ast"
"go/types"
)
type pkgTypes struct {
ifaces map[string]string
funcSigns map[string]bool
}
func (p *pkgTypes) getTypes(pkg *types.Package) {
p.ifaces = make(map[string]string)
p.funcSigns = make(map[string]bool)
done := make(map[*types.Package]bool)
addTypes := func(pkg *types.Package, top bool) {
if done[pkg] {
return
}
done[pkg] = true
ifs, funs := fromScope(pkg.Scope())
fullName := func(name string) string {
if !top {
return pkg.Path() + "." + name
}
return name
}
for iftype, name := range ifs {
// only suggest exported interfaces
if ast.IsExported(name) {
p.ifaces[iftype] = fullName(name)
}
}
for ftype := range funs {
// ignore non-exported func signatures too
p.funcSigns[ftype] = true
}
}
for _, imp := range pkg.Imports() {
addTypes(imp, false)
for _, imp2 := range imp.Imports() {
addTypes(imp2, false)
}
}
addTypes(pkg, true)
}

View File

@ -0,0 +1,462 @@
// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package check // import "mvdan.cc/interfacer/check"
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"os"
"strings"
"github.com/golangci/go-tools/ssa"
"github.com/golangci/go-tools/ssa/ssautil"
"golang.org/x/tools/go/loader"
"github.com/kisielk/gotool"
"mvdan.cc/lint"
)
func toDiscard(usage *varUsage) bool {
if usage.discard {
return true
}
for to := range usage.assigned {
if toDiscard(to) {
return true
}
}
return false
}
func allCalls(usage *varUsage, all, ftypes map[string]string) {
for fname := range usage.calls {
all[fname] = ftypes[fname]
}
for to := range usage.assigned {
allCalls(to, all, ftypes)
}
}
func (c *Checker) interfaceMatching(param *types.Var, usage *varUsage) (string, string) {
if toDiscard(usage) {
return "", ""
}
ftypes := typeFuncMap(param.Type())
called := make(map[string]string, len(usage.calls))
allCalls(usage, called, ftypes)
s := funcMapString(called)
return c.ifaces[s], s
}
type varUsage struct {
calls map[string]struct{}
discard bool
assigned map[*varUsage]struct{}
}
type funcDecl struct {
astDecl *ast.FuncDecl
ssaFn *ssa.Function
}
// CheckArgs checks the packages specified by their import paths in
// args.
func CheckArgs(args []string) ([]string, error) {
paths := gotool.ImportPaths(args)
conf := loader.Config{}
conf.AllowErrors = true
rest, err := conf.FromArgs(paths, false)
if err != nil {
return nil, err
}
if len(rest) > 0 {
return nil, fmt.Errorf("unwanted extra args: %v", rest)
}
lprog, err := conf.Load()
if err != nil {
return nil, err
}
prog := ssautil.CreateProgram(lprog, 0)
prog.Build()
c := new(Checker)
c.Program(lprog)
c.ProgramSSA(prog)
issues, err := c.Check()
if err != nil {
return nil, err
}
wd, err := os.Getwd()
if err != nil {
return nil, err
}
lines := make([]string, len(issues))
for i, issue := range issues {
fpos := prog.Fset.Position(issue.Pos()).String()
if strings.HasPrefix(fpos, wd) {
fpos = fpos[len(wd)+1:]
}
lines[i] = fmt.Sprintf("%s: %s", fpos, issue.Message())
}
return lines, nil
}
type Checker struct {
lprog *loader.Program
prog *ssa.Program
pkgTypes
*loader.PackageInfo
funcs []*funcDecl
ssaByPos map[token.Pos]*ssa.Function
discardFuncs map[*types.Signature]struct{}
vars map[*types.Var]*varUsage
}
var (
_ lint.Checker = (*Checker)(nil)
_ lint.WithSSA = (*Checker)(nil)
)
func (c *Checker) Program(lprog *loader.Program) {
c.lprog = lprog
}
func (c *Checker) ProgramSSA(prog *ssa.Program) {
c.prog = prog
}
func (c *Checker) Check() ([]lint.Issue, error) {
var total []lint.Issue
c.ssaByPos = make(map[token.Pos]*ssa.Function)
wantPkg := make(map[*types.Package]bool)
for _, pinfo := range c.lprog.InitialPackages() {
wantPkg[pinfo.Pkg] = true
}
for fn := range ssautil.AllFunctions(c.prog) {
if fn.Pkg == nil { // builtin?
continue
}
if len(fn.Blocks) == 0 { // stub
continue
}
if !wantPkg[fn.Pkg.Pkg] { // not part of given pkgs
continue
}
c.ssaByPos[fn.Pos()] = fn
}
for _, pinfo := range c.lprog.InitialPackages() {
pkg := pinfo.Pkg
c.getTypes(pkg)
c.PackageInfo = c.lprog.AllPackages[pkg]
total = append(total, c.checkPkg()...)
}
return total, nil
}
func (c *Checker) checkPkg() []lint.Issue {
c.discardFuncs = make(map[*types.Signature]struct{})
c.vars = make(map[*types.Var]*varUsage)
c.funcs = c.funcs[:0]
findFuncs := func(node ast.Node) bool {
decl, ok := node.(*ast.FuncDecl)
if !ok {
return true
}
ssaFn := c.ssaByPos[decl.Name.Pos()]
if ssaFn == nil {
return true
}
fd := &funcDecl{
astDecl: decl,
ssaFn: ssaFn,
}
if c.funcSigns[signString(fd.ssaFn.Signature)] {
// implements interface
return true
}
c.funcs = append(c.funcs, fd)
ast.Walk(c, decl.Body)
return true
}
for _, f := range c.Files {
ast.Inspect(f, findFuncs)
}
return c.packageIssues()
}
func paramVarAndType(sign *types.Signature, i int) (*types.Var, types.Type) {
params := sign.Params()
extra := sign.Variadic() && i >= params.Len()-1
if !extra {
if i >= params.Len() {
// builtins with multiple signatures
return nil, nil
}
vr := params.At(i)
return vr, vr.Type()
}
last := params.At(params.Len() - 1)
switch x := last.Type().(type) {
case *types.Slice:
return nil, x.Elem()
default:
return nil, x
}
}
func (c *Checker) varUsage(e ast.Expr) *varUsage {
id, ok := e.(*ast.Ident)
if !ok {
return nil
}
param, ok := c.ObjectOf(id).(*types.Var)
if !ok {
// not a variable
return nil
}
if usage, e := c.vars[param]; e {
return usage
}
if !interesting(param.Type()) {
return nil
}
usage := &varUsage{
calls: make(map[string]struct{}),
assigned: make(map[*varUsage]struct{}),
}
c.vars[param] = usage
return usage
}
func (c *Checker) addUsed(e ast.Expr, as types.Type) {
if as == nil {
return
}
if usage := c.varUsage(e); usage != nil {
// using variable
iface, ok := as.Underlying().(*types.Interface)
if !ok {
usage.discard = true
return
}
for i := 0; i < iface.NumMethods(); i++ {
m := iface.Method(i)
usage.calls[m.Name()] = struct{}{}
}
} else if t, ok := c.TypeOf(e).(*types.Signature); ok {
// using func
c.discardFuncs[t] = struct{}{}
}
}
func (c *Checker) addAssign(to, from ast.Expr) {
pto := c.varUsage(to)
pfrom := c.varUsage(from)
if pto == nil || pfrom == nil {
// either isn't interesting
return
}
pfrom.assigned[pto] = struct{}{}
}
func (c *Checker) discard(e ast.Expr) {
if usage := c.varUsage(e); usage != nil {
usage.discard = true
}
}
func (c *Checker) comparedWith(e, with ast.Expr) {
if _, ok := with.(*ast.BasicLit); ok {
c.discard(e)
}
}
func (c *Checker) Visit(node ast.Node) ast.Visitor {
switch x := node.(type) {
case *ast.SelectorExpr:
if _, ok := c.TypeOf(x.Sel).(*types.Signature); !ok {
c.discard(x.X)
}
case *ast.StarExpr:
c.discard(x.X)
case *ast.UnaryExpr:
c.discard(x.X)
case *ast.IndexExpr:
c.discard(x.X)
case *ast.IncDecStmt:
c.discard(x.X)
case *ast.BinaryExpr:
switch x.Op {
case token.EQL, token.NEQ:
c.comparedWith(x.X, x.Y)
c.comparedWith(x.Y, x.X)
default:
c.discard(x.X)
c.discard(x.Y)
}
case *ast.ValueSpec:
for _, val := range x.Values {
c.addUsed(val, c.TypeOf(x.Type))
}
case *ast.AssignStmt:
for i, val := range x.Rhs {
left := x.Lhs[i]
if x.Tok == token.ASSIGN {
c.addUsed(val, c.TypeOf(left))
}
c.addAssign(left, val)
}
case *ast.CompositeLit:
for i, e := range x.Elts {
switch y := e.(type) {
case *ast.KeyValueExpr:
c.addUsed(y.Key, c.TypeOf(y.Value))
c.addUsed(y.Value, c.TypeOf(y.Key))
case *ast.Ident:
c.addUsed(y, compositeIdentType(c.TypeOf(x), i))
}
}
case *ast.CallExpr:
switch y := c.TypeOf(x.Fun).Underlying().(type) {
case *types.Signature:
c.onMethodCall(x, y)
default:
// type conversion
if len(x.Args) == 1 {
c.addUsed(x.Args[0], y)
}
}
}
return c
}
func compositeIdentType(t types.Type, i int) types.Type {
switch x := t.(type) {
case *types.Named:
return compositeIdentType(x.Underlying(), i)
case *types.Struct:
return x.Field(i).Type()
case *types.Array:
return x.Elem()
case *types.Slice:
return x.Elem()
}
return nil
}
func (c *Checker) onMethodCall(ce *ast.CallExpr, sign *types.Signature) {
for i, e := range ce.Args {
paramObj, t := paramVarAndType(sign, i)
// Don't if this is a parameter being re-used as itself
// in a recursive call
if id, ok := e.(*ast.Ident); ok {
if paramObj == c.ObjectOf(id) {
continue
}
}
c.addUsed(e, t)
}
sel, ok := ce.Fun.(*ast.SelectorExpr)
if !ok {
return
}
// receiver func call on the left side
if usage := c.varUsage(sel.X); usage != nil {
usage.calls[sel.Sel.Name] = struct{}{}
}
}
func (fd *funcDecl) paramGroups() [][]*types.Var {
astList := fd.astDecl.Type.Params.List
groups := make([][]*types.Var, len(astList))
signIndex := 0
for i, field := range astList {
group := make([]*types.Var, len(field.Names))
for j := range field.Names {
group[j] = fd.ssaFn.Signature.Params().At(signIndex)
signIndex++
}
groups[i] = group
}
return groups
}
func (c *Checker) packageIssues() []lint.Issue {
var issues []lint.Issue
for _, fd := range c.funcs {
if _, e := c.discardFuncs[fd.ssaFn.Signature]; e {
continue
}
for _, group := range fd.paramGroups() {
issues = append(issues, c.groupIssues(fd, group)...)
}
}
return issues
}
type Issue struct {
pos token.Pos
msg string
}
func (i Issue) Pos() token.Pos { return i.pos }
func (i Issue) Message() string { return i.msg }
func (c *Checker) groupIssues(fd *funcDecl, group []*types.Var) []lint.Issue {
var issues []lint.Issue
for _, param := range group {
usage := c.vars[param]
if usage == nil {
return nil
}
newType := c.paramNewType(fd.astDecl.Name.Name, param, usage)
if newType == "" {
return nil
}
issues = append(issues, Issue{
pos: param.Pos(),
msg: fmt.Sprintf("%s can be %s", param.Name(), newType),
})
}
return issues
}
func willAddAllocation(t types.Type) bool {
switch t.Underlying().(type) {
case *types.Pointer, *types.Interface:
return false
}
return true
}
func (c *Checker) paramNewType(funcName string, param *types.Var, usage *varUsage) string {
t := param.Type()
if !ast.IsExported(funcName) && willAddAllocation(t) {
return ""
}
if named := typeNamed(t); named != nil {
tname := named.Obj().Name()
vname := param.Name()
if mentionsName(funcName, tname) || mentionsName(funcName, vname) {
return ""
}
}
ifname, iftype := c.interfaceMatching(param, usage)
if ifname == "" {
return ""
}
if types.IsInterface(t.Underlying()) {
if have := funcMapString(typeFuncMap(t)); have == iftype {
return ""
}
}
return ifname
}

View File

@ -0,0 +1,170 @@
// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package check
import (
"bytes"
"fmt"
"go/types"
"sort"
"strings"
)
type methoder interface {
NumMethods() int
Method(int) *types.Func
}
func methoderFuncMap(m methoder, skip bool) map[string]string {
ifuncs := make(map[string]string, m.NumMethods())
for i := 0; i < m.NumMethods(); i++ {
f := m.Method(i)
if !f.Exported() {
if skip {
continue
}
return nil
}
sign := f.Type().(*types.Signature)
ifuncs[f.Name()] = signString(sign)
}
return ifuncs
}
func typeFuncMap(t types.Type) map[string]string {
switch x := t.(type) {
case *types.Pointer:
return typeFuncMap(x.Elem())
case *types.Named:
u := x.Underlying()
if types.IsInterface(u) {
return typeFuncMap(u)
}
return methoderFuncMap(x, true)
case *types.Interface:
return methoderFuncMap(x, false)
default:
return nil
}
}
func funcMapString(iface map[string]string) string {
fnames := make([]string, 0, len(iface))
for fname := range iface {
fnames = append(fnames, fname)
}
sort.Strings(fnames)
var b bytes.Buffer
for i, fname := range fnames {
if i > 0 {
fmt.Fprint(&b, "; ")
}
fmt.Fprint(&b, fname, iface[fname])
}
return b.String()
}
func tupleJoin(buf *bytes.Buffer, t *types.Tuple) {
buf.WriteByte('(')
for i := 0; i < t.Len(); i++ {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(t.At(i).Type().String())
}
buf.WriteByte(')')
}
// signString is similar to Signature.String(), but it ignores
// param/result names.
func signString(sign *types.Signature) string {
var buf bytes.Buffer
tupleJoin(&buf, sign.Params())
tupleJoin(&buf, sign.Results())
return buf.String()
}
func interesting(t types.Type) bool {
switch x := t.(type) {
case *types.Interface:
return x.NumMethods() > 1
case *types.Named:
if u := x.Underlying(); types.IsInterface(u) {
return interesting(u)
}
return x.NumMethods() >= 1
case *types.Pointer:
return interesting(x.Elem())
default:
return false
}
}
func anyInteresting(params *types.Tuple) bool {
for i := 0; i < params.Len(); i++ {
t := params.At(i).Type()
if interesting(t) {
return true
}
}
return false
}
func fromScope(scope *types.Scope) (ifaces map[string]string, funcs map[string]bool) {
ifaces = make(map[string]string)
funcs = make(map[string]bool)
for _, name := range scope.Names() {
tn, ok := scope.Lookup(name).(*types.TypeName)
if !ok {
continue
}
switch x := tn.Type().Underlying().(type) {
case *types.Interface:
iface := methoderFuncMap(x, false)
if len(iface) == 0 {
continue
}
for i := 0; i < x.NumMethods(); i++ {
f := x.Method(i)
sign := f.Type().(*types.Signature)
if !anyInteresting(sign.Params()) {
continue
}
funcs[signString(sign)] = true
}
s := funcMapString(iface)
if _, e := ifaces[s]; !e {
ifaces[s] = tn.Name()
}
case *types.Signature:
if !anyInteresting(x.Params()) {
continue
}
funcs[signString(x)] = true
}
}
return ifaces, funcs
}
func mentionsName(fname, name string) bool {
if len(name) < 2 {
return false
}
capit := strings.ToUpper(name[:1]) + name[1:]
lower := strings.ToLower(name)
return strings.Contains(fname, capit) || strings.HasPrefix(fname, lower)
}
func typeNamed(t types.Type) *types.Named {
for {
switch x := t.(type) {
case *types.Named:
return x
case *types.Pointer:
t = x.Elem()
default:
return nil
}
}
}

7
vendor/mvdan.cc/lint/.travis.yml vendored Normal file
View File

@ -0,0 +1,7 @@
language: go
go:
- 1.8.x
- 1.9.x
go_import_path: mvdan.cc/lint

27
vendor/mvdan.cc/lint/LICENSE vendored Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2017, Daniel Martí. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

27
vendor/mvdan.cc/lint/README.md vendored Normal file
View File

@ -0,0 +1,27 @@
# lint
[![GoDoc](https://godoc.org/mvdan.cc/lint?status.svg)](https://godoc.org/mvdan.cc/lint)
[![Build Status](https://travis-ci.org/mvdan/lint.svg?branch=master)](https://travis-ci.org/mvdan/lint)
Work in progress. Its API might change before the 1.0 release.
This package intends to define simple interfaces that Go code checkers
can implement. This would simplify calling them from Go code, as well as
running multiple linters while sharing initial loading work.
### metalint
go get -u mvdan.cc/lint/cmd/metalint
The start of a linter that runs many linters leveraging the common
interface. Not stable yet.
Linters included:
* [unparam](https://mvdan.cc/unparam)
* [interfacer](https://github.com/mvdan/interfacer)
### Related projects
* [golinters](https://github.com/thomasheller/golinters) - Report on
linter support

28
vendor/mvdan.cc/lint/lint.go vendored Normal file
View File

@ -0,0 +1,28 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
// Package lint defines common interfaces for Go code checkers.
package lint // import "mvdan.cc/lint"
import (
"go/token"
"github.com/golangci/go-tools/ssa"
"golang.org/x/tools/go/loader"
)
// A Checker points out issues in a program.
type Checker interface {
Program(*loader.Program)
Check() ([]Issue, error)
}
type WithSSA interface {
ProgramSSA(*ssa.Program)
}
// Issue represents an issue somewhere in a source code file.
type Issue interface {
Pos() token.Pos
Message() string
}