Update gometalinter

This commit is contained in:
Erik Johnston 2017-11-29 18:12:15 +00:00
parent cc12fc930a
commit 7a060094fc
44 changed files with 2273 additions and 652 deletions

2
vendor/manifest vendored
View file

@ -10,7 +10,7 @@
{
"importpath": "github.com/alecthomas/gometalinter",
"repository": "https://github.com/alecthomas/gometalinter",
"revision": "0262fb20957a4c2d3bb7c834a6a125ae3884a2c6",
"revision": "212b1b91e362ea0b0e441c9b53ce31e81405c240",
"branch": "master"
},
{

View file

@ -0,0 +1,27 @@
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.

View file

@ -0,0 +1,167 @@
// Copyright 2014 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.
// Package importgraph computes the forward and reverse import
// dependency graphs for all packages in a Go workspace.
package importgraph // import "golang.org/x/tools/refactor/importgraph"
import (
"go/build"
"sync"
"golang.org/x/tools/go/buildutil"
)
// A Graph is an import dependency graph, either forward or reverse.
//
// The graph maps each node (a package import path) to the set of its
// successors in the graph. For a forward graph, this is the set of
// imported packages (prerequisites); for a reverse graph, it is the set
// of importing packages (clients).
//
// Graph construction inspects all imports in each package's directory,
// including those in _test.go files, so the resulting graph may be cyclic.
type Graph map[string]map[string]bool
func (g Graph) addEdge(from, to string) {
edges := g[from]
if edges == nil {
edges = make(map[string]bool)
g[from] = edges
}
edges[to] = true
}
// Search returns all the nodes of the graph reachable from
// any of the specified roots, by following edges forwards.
// Relationally, this is the reflexive transitive closure.
func (g Graph) Search(roots ...string) map[string]bool {
seen := make(map[string]bool)
var visit func(x string)
visit = func(x string) {
if !seen[x] {
seen[x] = true
for y := range g[x] {
visit(y)
}
}
}
for _, root := range roots {
visit(root)
}
return seen
}
// Build scans the specified Go workspace and builds the forward and
// reverse import dependency graphs for all its packages.
// It also returns a mapping from canonical import paths to errors for packages
// whose loading was not entirely successful.
// A package may appear in the graph and in the errors mapping.
// All package paths are canonical and may contain "/vendor/".
func Build(ctxt *build.Context) (forward, reverse Graph, errors map[string]error) {
type importEdge struct {
from, to string
}
type pathError struct {
path string
err error
}
ch := make(chan interface{})
go func() {
sema := make(chan int, 20) // I/O concurrency limiting semaphore
var wg sync.WaitGroup
buildutil.ForEachPackage(ctxt, func(path string, err error) {
if err != nil {
ch <- pathError{path, err}
return
}
wg.Add(1)
go func() {
defer wg.Done()
sema <- 1
bp, err := ctxt.Import(path, "", 0)
<-sema
if err != nil {
if _, ok := err.(*build.NoGoError); ok {
// empty directory is not an error
} else {
ch <- pathError{path, err}
}
// Even in error cases, Import usually returns a package.
}
// absolutize resolves an import path relative
// to the current package bp.
// The absolute form may contain "vendor".
//
// The vendoring feature slows down Build by 3×.
// Here are timings from a 1400 package workspace:
// 1100ms: current code (with vendor check)
// 880ms: with a nonblocking cache around ctxt.IsDir
// 840ms: nonblocking cache with duplicate suppression
// 340ms: original code (no vendor check)
// TODO(adonovan): optimize, somehow.
memo := make(map[string]string)
absolutize := func(path string) string {
canon, ok := memo[path]
if !ok {
sema <- 1
bp2, _ := ctxt.Import(path, bp.Dir, build.FindOnly)
<-sema
if bp2 != nil {
canon = bp2.ImportPath
} else {
canon = path
}
memo[path] = canon
}
return canon
}
if bp != nil {
for _, imp := range bp.Imports {
ch <- importEdge{path, absolutize(imp)}
}
for _, imp := range bp.TestImports {
ch <- importEdge{path, absolutize(imp)}
}
for _, imp := range bp.XTestImports {
ch <- importEdge{path, absolutize(imp)}
}
}
}()
})
wg.Wait()
close(ch)
}()
forward = make(Graph)
reverse = make(Graph)
for e := range ch {
switch e := e.(type) {
case pathError:
if errors == nil {
errors = make(map[string]error)
}
errors[e.path] = e.err
case importEdge:
if e.to == "C" {
continue // "C" is fake
}
forward.addEdge(e.from, e.to)
reverse.addEdge(e.to, e.from)
}
}
return forward, reverse, errors
}

View file

@ -0,0 +1,16 @@
package main // import "honnef.co/go/tools/cmd/errcheck-ng"
import (
"os"
"honnef.co/go/tools/errcheck"
"honnef.co/go/tools/lint/lintutil"
)
func main() {
c := lintutil.CheckerConfig{
Checker: errcheck.NewChecker(),
ExitNonZero: true,
}
lintutil.ProcessArgs("errcheck-ng", []lintutil.CheckerConfig{c}, os.Args[1:])
}

View file

@ -1,20 +0,0 @@
Copyright (c) 2016 Dominik Honnef
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.

View file

@ -13,6 +13,9 @@ func main() {
fs.Parse(os.Args[1:])
c := simple.NewChecker()
c.CheckGenerated = *gen
lintutil.ProcessFlagSet(c, fs)
cfg := lintutil.CheckerConfig{
Checker: c,
ExitNonZero: true,
}
lintutil.ProcessFlagSet([]lintutil.CheckerConfig{cfg}, fs)
}

View file

@ -0,0 +1,401 @@
// keyify transforms unkeyed struct literals into a keyed ones.
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"go/ast"
"go/build"
"go/constant"
"go/printer"
"go/token"
"go/types"
"log"
"os"
"path/filepath"
"honnef.co/go/tools/version"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/go/loader"
)
var (
fRecursive bool
fOneLine bool
fJSON bool
fMinify bool
fModified bool
fVersion bool
)
func init() {
flag.BoolVar(&fRecursive, "r", false, "keyify struct initializers recursively")
flag.BoolVar(&fOneLine, "o", false, "print new struct initializer on a single line")
flag.BoolVar(&fJSON, "json", false, "print new struct initializer as JSON")
flag.BoolVar(&fMinify, "m", false, "omit fields that are set to their zero value")
flag.BoolVar(&fModified, "modified", false, "read an archive of modified files from standard input")
flag.BoolVar(&fVersion, "version", false, "Print version and exit")
}
func usage() {
fmt.Printf("Usage: %s [flags] <position>\n\n", os.Args[0])
flag.PrintDefaults()
}
func main() {
log.SetFlags(0)
flag.Usage = usage
flag.Parse()
if fVersion {
version.Print()
os.Exit(0)
}
if flag.NArg() != 1 {
flag.Usage()
os.Exit(2)
}
pos := flag.Args()[0]
name, start, _, err := parsePos(pos)
if err != nil {
log.Fatal(err)
}
eval, err := filepath.EvalSymlinks(name)
if err != nil {
log.Fatal(err)
}
name, err = filepath.Abs(eval)
if err != nil {
log.Fatal(err)
}
cwd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
ctx := &build.Default
if fModified {
overlay, err := buildutil.ParseOverlayArchive(os.Stdin)
if err != nil {
log.Fatal(err)
}
ctx = buildutil.OverlayContext(ctx, overlay)
}
bpkg, err := buildutil.ContainingPackage(ctx, cwd, name)
if err != nil {
log.Fatal(err)
}
conf := &loader.Config{
Build: ctx,
}
conf.TypeCheckFuncBodies = func(s string) bool {
return s == bpkg.ImportPath || s == bpkg.ImportPath+"_test"
}
conf.ImportWithTests(bpkg.ImportPath)
lprog, err := conf.Load()
if err != nil {
log.Fatal(err)
}
var tf *token.File
var af *ast.File
pkg := lprog.InitialPackages()[0]
for _, ff := range pkg.Files {
file := lprog.Fset.File(ff.Pos())
if file.Name() == name {
af = ff
tf = file
break
}
}
tstart, tend, err := fileOffsetToPos(tf, start, start)
if err != nil {
log.Fatal(err)
}
path, _ := astutil.PathEnclosingInterval(af, tstart, tend)
var complit *ast.CompositeLit
for _, p := range path {
if p, ok := p.(*ast.CompositeLit); ok {
complit = p
break
}
}
if complit == nil {
log.Fatal("no composite literal found near point")
}
if len(complit.Elts) == 0 {
printComplit(complit, complit, lprog.Fset, lprog.Fset)
return
}
if _, ok := complit.Elts[0].(*ast.KeyValueExpr); ok {
lit := complit
if fOneLine {
lit = copyExpr(complit, 1).(*ast.CompositeLit)
}
printComplit(complit, lit, lprog.Fset, lprog.Fset)
return
}
_, ok := pkg.TypeOf(complit).Underlying().(*types.Struct)
if !ok {
log.Fatal("not a struct initialiser")
return
}
newComplit, lines := keyify(pkg, complit)
newFset := token.NewFileSet()
newFile := newFset.AddFile("", -1, lines)
for i := 1; i <= lines; i++ {
newFile.AddLine(i)
}
printComplit(complit, newComplit, lprog.Fset, newFset)
}
func keyify(
pkg *loader.PackageInfo,
complit *ast.CompositeLit,
) (*ast.CompositeLit, int) {
var calcPos func(int) token.Pos
if fOneLine {
calcPos = func(int) token.Pos { return token.Pos(1) }
} else {
calcPos = func(i int) token.Pos { return token.Pos(2 + i) }
}
st, _ := pkg.TypeOf(complit).Underlying().(*types.Struct)
newComplit := &ast.CompositeLit{
Type: complit.Type,
Lbrace: 1,
Rbrace: token.Pos(st.NumFields() + 2),
}
if fOneLine {
newComplit.Rbrace = 1
}
numLines := 2 + st.NumFields()
n := 0
for i := 0; i < st.NumFields(); i++ {
field := st.Field(i)
val := complit.Elts[i]
if fRecursive {
if val2, ok := val.(*ast.CompositeLit); ok {
if _, ok := pkg.TypeOf(val2.Type).Underlying().(*types.Struct); ok {
var lines int
numLines += lines
val, lines = keyify(pkg, val2)
}
}
}
_, isIface := st.Field(i).Type().Underlying().(*types.Interface)
if fMinify && (isNil(val, pkg) || (!isIface && isZero(val, pkg))) {
continue
}
elt := &ast.KeyValueExpr{
Key: &ast.Ident{NamePos: calcPos(n), Name: field.Name()},
Value: copyExpr(val, calcPos(n)),
}
newComplit.Elts = append(newComplit.Elts, elt)
n++
}
return newComplit, numLines
}
func isNil(val ast.Expr, pkg *loader.PackageInfo) bool {
ident, ok := val.(*ast.Ident)
if !ok {
return false
}
if _, ok := pkg.ObjectOf(ident).(*types.Nil); ok {
return true
}
if c, ok := pkg.ObjectOf(ident).(*types.Const); ok {
if c.Val().Kind() != constant.Bool {
return false
}
return !constant.BoolVal(c.Val())
}
return false
}
func isZero(val ast.Expr, pkg *loader.PackageInfo) bool {
switch val := val.(type) {
case *ast.BasicLit:
switch val.Value {
case `""`, "``", "0", "0.0", "0i", "0.":
return true
default:
return false
}
case *ast.Ident:
return isNil(val, pkg)
case *ast.CompositeLit:
typ := pkg.TypeOf(val.Type)
if typ == nil {
return false
}
isIface := false
switch typ := typ.Underlying().(type) {
case *types.Struct:
case *types.Array:
_, isIface = typ.Elem().Underlying().(*types.Interface)
default:
return false
}
for _, elt := range val.Elts {
if isNil(elt, pkg) || (!isIface && !isZero(elt, pkg)) {
return false
}
}
return true
}
return false
}
func printComplit(oldlit, newlit *ast.CompositeLit, oldfset, newfset *token.FileSet) {
buf := &bytes.Buffer{}
cfg := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
_ = cfg.Fprint(buf, newfset, newlit)
if fJSON {
output := struct {
Start int `json:"start"`
End int `json:"end"`
Replacement string `json:"replacement"`
}{
oldfset.Position(oldlit.Pos()).Offset,
oldfset.Position(oldlit.End()).Offset,
buf.String(),
}
_ = json.NewEncoder(os.Stdout).Encode(output)
} else {
fmt.Println(buf.String())
}
}
func copyExpr(expr ast.Expr, line token.Pos) ast.Expr {
switch expr := expr.(type) {
case *ast.BasicLit:
cp := *expr
cp.ValuePos = 0
return &cp
case *ast.BinaryExpr:
cp := *expr
cp.X = copyExpr(cp.X, line)
cp.OpPos = 0
cp.Y = copyExpr(cp.Y, line)
return &cp
case *ast.CallExpr:
cp := *expr
cp.Fun = copyExpr(cp.Fun, line)
cp.Lparen = 0
for i, v := range cp.Args {
cp.Args[i] = copyExpr(v, line)
}
if cp.Ellipsis != 0 {
cp.Ellipsis = line
}
cp.Rparen = 0
return &cp
case *ast.CompositeLit:
cp := *expr
cp.Type = copyExpr(cp.Type, line)
cp.Lbrace = 0
for i, v := range cp.Elts {
cp.Elts[i] = copyExpr(v, line)
}
cp.Rbrace = 0
return &cp
case *ast.Ident:
cp := *expr
cp.NamePos = 0
return &cp
case *ast.IndexExpr:
cp := *expr
cp.X = copyExpr(cp.X, line)
cp.Lbrack = 0
cp.Index = copyExpr(cp.Index, line)
cp.Rbrack = 0
return &cp
case *ast.KeyValueExpr:
cp := *expr
cp.Key = copyExpr(cp.Key, line)
cp.Colon = 0
cp.Value = copyExpr(cp.Value, line)
return &cp
case *ast.ParenExpr:
cp := *expr
cp.Lparen = 0
cp.X = copyExpr(cp.X, line)
cp.Rparen = 0
return &cp
case *ast.SelectorExpr:
cp := *expr
cp.X = copyExpr(cp.X, line)
cp.Sel = copyExpr(cp.Sel, line).(*ast.Ident)
return &cp
case *ast.SliceExpr:
cp := *expr
cp.X = copyExpr(cp.X, line)
cp.Lbrack = 0
cp.Low = copyExpr(cp.Low, line)
cp.High = copyExpr(cp.High, line)
cp.Max = copyExpr(cp.Max, line)
cp.Rbrack = 0
return &cp
case *ast.StarExpr:
cp := *expr
cp.Star = 0
cp.X = copyExpr(cp.X, line)
return &cp
case *ast.TypeAssertExpr:
cp := *expr
cp.X = copyExpr(cp.X, line)
cp.Lparen = 0
cp.Type = copyExpr(cp.Type, line)
cp.Rparen = 0
return &cp
case *ast.UnaryExpr:
cp := *expr
cp.OpPos = 0
cp.X = copyExpr(cp.X, line)
return &cp
case *ast.MapType:
cp := *expr
cp.Map = 0
cp.Key = copyExpr(cp.Key, line)
cp.Value = copyExpr(cp.Value, line)
return &cp
case *ast.ArrayType:
cp := *expr
cp.Lbrack = 0
cp.Len = copyExpr(cp.Len, line)
cp.Elt = copyExpr(cp.Elt, line)
return &cp
case *ast.Ellipsis:
cp := *expr
cp.Elt = copyExpr(cp.Elt, line)
cp.Ellipsis = line
return &cp
case *ast.InterfaceType:
cp := *expr
cp.Interface = 0
return &cp
case *ast.StructType:
cp := *expr
cp.Struct = 0
return &cp
case *ast.FuncLit:
return expr
case *ast.ChanType:
cp := *expr
cp.Arrow = 0
cp.Begin = 0
cp.Value = copyExpr(cp.Value, line)
return &cp
case nil:
return nil
default:
panic(fmt.Sprintf("shouldn't happen: unknown ast.Expr of type %T", expr))
}
return nil
}

View file

@ -0,0 +1,71 @@
// Copyright 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.
package main
import (
"fmt"
"go/token"
"strconv"
"strings"
)
func parseOctothorpDecimal(s string) int {
if s != "" && s[0] == '#' {
if s, err := strconv.ParseInt(s[1:], 10, 32); err == nil {
return int(s)
}
}
return -1
}
func parsePos(pos string) (filename string, startOffset, endOffset int, err error) {
if pos == "" {
err = fmt.Errorf("no source position specified")
return
}
colon := strings.LastIndex(pos, ":")
if colon < 0 {
err = fmt.Errorf("bad position syntax %q", pos)
return
}
filename, offset := pos[:colon], pos[colon+1:]
startOffset = -1
endOffset = -1
if hyphen := strings.Index(offset, ","); hyphen < 0 {
// e.g. "foo.go:#123"
startOffset = parseOctothorpDecimal(offset)
endOffset = startOffset
} else {
// e.g. "foo.go:#123,#456"
startOffset = parseOctothorpDecimal(offset[:hyphen])
endOffset = parseOctothorpDecimal(offset[hyphen+1:])
}
if startOffset < 0 || endOffset < 0 {
err = fmt.Errorf("invalid offset %q in query position", offset)
return
}
return
}
func fileOffsetToPos(file *token.File, startOffset, endOffset int) (start, end token.Pos, err error) {
// Range check [start..end], inclusive of both end-points.
if 0 <= startOffset && startOffset <= file.Size() {
start = file.Pos(int(startOffset))
} else {
err = fmt.Errorf("start position is beyond end of file")
return
}
if 0 <= endOffset && endOffset <= file.Size() {
end = file.Pos(int(endOffset))
} else {
err = fmt.Errorf("end position is beyond end of file")
return
}
return
}

View file

@ -1,20 +0,0 @@
Copyright (c) 2016 Dominik Honnef
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.

View file

@ -4,42 +4,23 @@ package main // import "honnef.co/go/tools/cmd/megacheck"
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/unused"
)
type Checker struct {
Checkers []lint.Checker
}
func (c *Checker) Init(prog *lint.Program) {
for _, cc := range c.Checkers {
cc.Init(prog)
}
}
func (c *Checker) Funcs() map[string]lint.Func {
fns := map[string]lint.Func{}
for _, cc := range c.Checkers {
for k, v := range cc.Funcs() {
fns[k] = v
}
}
return fns
}
func main() {
var flags struct {
staticcheck struct {
enabled bool
generated bool
enabled bool
generated bool
exitNonZero bool
}
gosimple struct {
enabled bool
generated bool
enabled bool
generated bool
exitNonZero bool
}
unused struct {
enabled bool
@ -51,6 +32,7 @@ func main() {
debug string
wholeProgram bool
reflection bool
exitNonZero bool
}
}
fs := lintutil.FlagSet("megacheck")
@ -58,11 +40,15 @@ func main() {
"simple.enabled", true, "Run gosimple")
fs.BoolVar(&flags.gosimple.generated,
"simple.generated", false, "Check generated code")
fs.BoolVar(&flags.gosimple.exitNonZero,
"simple.exit-non-zero", false, "Exit non-zero if any problems were found")
fs.BoolVar(&flags.staticcheck.enabled,
"staticcheck.enabled", true, "Run staticcheck")
fs.BoolVar(&flags.staticcheck.generated,
"staticcheck.generated", false, "Check generated code (only applies to a subset of checks)")
fs.BoolVar(&flags.staticcheck.exitNonZero,
"staticcheck.exit-non-zero", true, "Exit non-zero if any problems were found")
fs.BoolVar(&flags.unused.enabled,
"unused.enabled", true, "Run unused")
@ -78,22 +64,31 @@ func main() {
"unused.vars", true, "Report unused variables")
fs.BoolVar(&flags.unused.wholeProgram,
"unused.exported", false, "Treat arguments as a program and report unused exported identifiers")
fs.BoolVar(&flags.unused.reflection, "unused.reflect", true, "Consider identifiers as used when it's likely they'll be accessed via reflection")
fs.BoolVar(&flags.unused.reflection,
"unused.reflect", true, "Consider identifiers as used when it's likely they'll be accessed via reflection")
fs.BoolVar(&flags.unused.exitNonZero,
"unused.exit-non-zero", true, "Exit non-zero if any problems were found")
fs.Parse(os.Args[1:])
c := &Checker{}
var checkers []lintutil.CheckerConfig
if flags.staticcheck.enabled {
sac := staticcheck.NewChecker()
sac.CheckGenerated = flags.staticcheck.generated
c.Checkers = append(c.Checkers, sac)
checkers = append(checkers, lintutil.CheckerConfig{
Checker: sac,
ExitNonZero: flags.staticcheck.exitNonZero,
})
}
if flags.gosimple.enabled {
sc := simple.NewChecker()
sc.CheckGenerated = flags.gosimple.generated
c.Checkers = append(c.Checkers, sc)
checkers = append(checkers, lintutil.CheckerConfig{
Checker: sc,
ExitNonZero: flags.gosimple.exitNonZero,
})
}
if flags.unused.enabled {
@ -116,8 +111,12 @@ func main() {
uc := unused.NewChecker(mode)
uc.WholeProgram = flags.unused.wholeProgram
uc.ConsiderReflection = flags.unused.reflection
c.Checkers = append(c.Checkers, unused.NewLintChecker(uc))
checkers = append(checkers, lintutil.CheckerConfig{
Checker: unused.NewLintChecker(uc),
ExitNonZero: flags.unused.exitNonZero,
})
}
lintutil.ProcessFlagSet(c, fs)
lintutil.ProcessFlagSet(checkers, fs)
}

View file

@ -0,0 +1,86 @@
// rdeps scans GOPATH for all reverse dependencies of a set of Go
// packages.
//
// rdeps will not sort its output, and the order of the output is
// undefined. Pipe its output through sort if you need stable output.
package main
import (
"bufio"
"flag"
"fmt"
"go/build"
"os"
"honnef.co/go/tools/version"
"github.com/kisielk/gotool"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/refactor/importgraph"
)
func main() {
var tags buildutil.TagsFlag
flag.Var(&tags, "tags", "List of build tags")
stdin := flag.Bool("stdin", false, "Read packages from stdin instead of the command line")
recursive := flag.Bool("r", false, "Print reverse dependencies recursively")
printVersion := flag.Bool("version", false, "Print version and exit")
flag.Parse()
if *printVersion {
version.Print()
os.Exit(0)
}
ctx := build.Default
ctx.BuildTags = tags
var args []string
if *stdin {
s := bufio.NewScanner(os.Stdin)
for s.Scan() {
args = append(args, s.Text())
}
} else {
args = flag.Args()
}
if len(args) == 0 {
return
}
wd, err := os.Getwd()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
pkgs := gotool.ImportPaths(args)
for i, pkg := range pkgs {
bpkg, err := ctx.Import(pkg, wd, build.FindOnly)
if err != nil {
continue
}
pkgs[i] = bpkg.ImportPath
}
_, reverse, errors := importgraph.Build(&ctx)
_ = errors
seen := map[string]bool{}
var printRDeps func(pkg string)
printRDeps = func(pkg string) {
for rdep := range reverse[pkg] {
if seen[rdep] {
continue
}
seen[rdep] = true
fmt.Println(rdep)
if *recursive {
printRDeps(rdep)
}
}
}
for _, pkg := range pkgs {
printRDeps(pkg)
}
for pkg, err := range errors {
fmt.Fprintf(os.Stderr, "error in package %s: %s\n", pkg, err)
}
}

View file

@ -1,20 +0,0 @@
Copyright (c) 2016 Dominik Honnef
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.

View file

@ -15,5 +15,9 @@ func main() {
fs.Parse(os.Args[1:])
c := staticcheck.NewChecker()
c.CheckGenerated = *gen
lintutil.ProcessFlagSet(c, fs)
cfg := lintutil.CheckerConfig{
Checker: c,
ExitNonZero: true,
}
lintutil.ProcessFlagSet([]lintutil.CheckerConfig{cfg}, fs)
}

View file

@ -0,0 +1,205 @@
// structlayout-optimize reorders struct fields to minimize the amount
// of padding.
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"os"
"sort"
"strings"
st "honnef.co/go/tools/structlayout"
"honnef.co/go/tools/version"
)
var (
fJSON bool
fRecurse bool
fVersion bool
)
func init() {
flag.BoolVar(&fJSON, "json", false, "Format data as JSON")
flag.BoolVar(&fRecurse, "r", false, "Break up structs and reorder their fields freely")
flag.BoolVar(&fVersion, "version", false, "Print version and exit")
}
func main() {
log.SetFlags(0)
flag.Parse()
if fVersion {
version.Print()
os.Exit(0)
}
var in []st.Field
if err := json.NewDecoder(os.Stdin).Decode(&in); err != nil {
log.Fatal(err)
}
if len(in) == 0 {
return
}
if !fRecurse {
in = combine(in)
}
var fields []st.Field
for _, field := range in {
if field.IsPadding {
continue
}
fields = append(fields, field)
}
optimize(fields)
fields = pad(fields)
if fJSON {
json.NewEncoder(os.Stdout).Encode(fields)
} else {
for _, field := range fields {
fmt.Println(field)
}
}
}
func combine(fields []st.Field) []st.Field {
new := st.Field{}
cur := ""
var out []st.Field
wasPad := true
for _, field := range fields {
var prefix string
if field.IsPadding {
wasPad = true
continue
}
p := strings.Split(field.Name, ".")
prefix = strings.Join(p[:2], ".")
if field.Align > new.Align {
new.Align = field.Align
}
if !wasPad {
new.End = field.Start
new.Size = new.End - new.Start
}
if prefix != cur {
if cur != "" {
out = append(out, new)
}
cur = prefix
new = field
new.Name = prefix
} else {
new.Type = "struct"
}
wasPad = false
}
new.Size = new.End - new.Start
out = append(out, new)
return out
}
func optimize(fields []st.Field) {
sort.Sort(&byAlignAndSize{fields})
}
func pad(fields []st.Field) []st.Field {
if len(fields) == 0 {
return nil
}
var out []st.Field
pos := int64(0)
offsets := offsetsof(fields)
alignment := int64(1)
for i, field := range fields {
if field.Align > alignment {
alignment = field.Align
}
if offsets[i] > pos {
padding := offsets[i] - pos
out = append(out, st.Field{
IsPadding: true,
Start: pos,
End: pos + padding,
Size: padding,
})
pos += padding
}
field.Start = pos
field.End = pos + field.Size
out = append(out, field)
pos += field.Size
}
sz := size(out)
pad := align(sz, alignment) - sz
if pad > 0 {
field := out[len(out)-1]
out = append(out, st.Field{
IsPadding: true,
Start: field.End,
End: field.End + pad,
Size: pad,
})
}
return out
}
func size(fields []st.Field) int64 {
n := int64(0)
for _, field := range fields {
n += field.Size
}
return n
}
type byAlignAndSize struct {
fields []st.Field
}
func (s *byAlignAndSize) Len() int { return len(s.fields) }
func (s *byAlignAndSize) Swap(i, j int) {
s.fields[i], s.fields[j] = s.fields[j], s.fields[i]
}
func (s *byAlignAndSize) Less(i, j int) bool {
// Place zero sized objects before non-zero sized objects.
if s.fields[i].Size == 0 && s.fields[j].Size != 0 {
return true
}
if s.fields[j].Size == 0 && s.fields[i].Size != 0 {
return false
}
// Next, place more tightly aligned objects before less tightly aligned objects.
if s.fields[i].Align != s.fields[j].Align {
return s.fields[i].Align > s.fields[j].Align
}
// Lastly, order by size.
if s.fields[i].Size != s.fields[j].Size {
return s.fields[i].Size > s.fields[j].Size
}
return false
}
func offsetsof(fields []st.Field) []int64 {
offsets := make([]int64, len(fields))
var o int64
for i, f := range fields {
a := f.Align
o = align(o, a)
offsets[i] = o
o += f.Size
}
return offsets
}
// align returns the smallest y >= x such that y % a == 0.
func align(x, a int64) int64 {
y := x + a - 1
return y - y%a
}

View file

@ -0,0 +1,72 @@
// structlayout-pretty formats the output of structlayout with ASCII
// art.
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"os"
"strings"
st "honnef.co/go/tools/structlayout"
"honnef.co/go/tools/version"
)
var (
fVerbose bool
fVersion bool
)
func init() {
flag.BoolVar(&fVerbose, "v", false, "Do not compact consecutive bytes of fields")
flag.BoolVar(&fVersion, "version", false, "Print version and exit")
}
func main() {
log.SetFlags(0)
flag.Parse()
if fVersion {
version.Print()
os.Exit(0)
}
var fields []st.Field
if err := json.NewDecoder(os.Stdin).Decode(&fields); err != nil {
log.Fatal(err)
}
if len(fields) == 0 {
return
}
max := fields[len(fields)-1].End
maxLength := len(fmt.Sprintf("%d", max))
padding := strings.Repeat(" ", maxLength+2)
format := fmt.Sprintf(" %%%dd ", maxLength)
pos := int64(0)
fmt.Println(padding + "+--------+")
for _, field := range fields {
name := field.Name + " " + field.Type
if field.IsPadding {
name = "padding"
}
fmt.Printf(format+"| | <- %s (size %d, align %d)\n", pos, name, field.Size, field.Align)
fmt.Println(padding + "+--------+")
if fVerbose {
for i := int64(0); i < field.Size-1; i++ {
fmt.Printf(format+"| |\n", pos+i+1)
fmt.Println(padding + "+--------+")
}
} else {
if field.Size > 2 {
fmt.Println(padding + "-........-")
fmt.Println(padding + "+--------+")
fmt.Printf(format+"| |\n", pos+field.Size-1)
fmt.Println(padding + "+--------+")
}
}
pos += field.Size
}
}

View file

@ -0,0 +1,149 @@
// structlayout displays the layout (field sizes and padding) of structs.
package main
import (
"encoding/json"
"flag"
"fmt"
"go/build"
"go/types"
"log"
"os"
"honnef.co/go/tools/gcsizes"
st "honnef.co/go/tools/structlayout"
"honnef.co/go/tools/version"
"golang.org/x/tools/go/loader"
)
var (
fJSON bool
fVersion bool
)
func init() {
flag.BoolVar(&fJSON, "json", false, "Format data as JSON")
flag.BoolVar(&fVersion, "version", false, "Print version and exit")
}
func main() {
log.SetFlags(0)
flag.Parse()
if fVersion {
version.Print()
os.Exit(0)
}
if len(flag.Args()) != 2 {
flag.Usage()
os.Exit(1)
}
conf := loader.Config{
Build: &build.Default,
}
var pkg string
var typName string
pkg = flag.Args()[0]
typName = flag.Args()[1]
conf.Import(pkg)
lprog, err := conf.Load()
if err != nil {
log.Fatal(err)
}
var typ types.Type
obj := lprog.Package(pkg).Pkg.Scope().Lookup(typName)
if obj == nil {
log.Fatal("couldn't find type")
}
typ = obj.Type()
st, ok := typ.Underlying().(*types.Struct)
if !ok {
log.Fatal("identifier is not a struct type")
}
fields := sizes(st, typ.(*types.Named).Obj().Name(), 0, nil)
if fJSON {
emitJSON(fields)
} else {
emitText(fields)
}
}
func emitJSON(fields []st.Field) {
if fields == nil {
fields = []st.Field{}
}
json.NewEncoder(os.Stdout).Encode(fields)
}
func emitText(fields []st.Field) {
for _, field := range fields {
fmt.Println(field)
}
}
func sizes(typ *types.Struct, prefix string, base int64, out []st.Field) []st.Field {
s := gcsizes.ForArch(build.Default.GOARCH)
n := typ.NumFields()
var fields []*types.Var
for i := 0; i < n; i++ {
fields = append(fields, typ.Field(i))
}
offsets := s.Offsetsof(fields)
for i := range offsets {
offsets[i] += base
}
pos := base
for i, field := range fields {
if offsets[i] > pos {
padding := offsets[i] - pos
out = append(out, st.Field{
IsPadding: true,
Start: pos,
End: pos + padding,
Size: padding,
})
pos += padding
}
size := s.Sizeof(field.Type())
if typ2, ok := field.Type().Underlying().(*types.Struct); ok && typ2.NumFields() != 0 {
out = sizes(typ2, prefix+"."+field.Name(), pos, out)
} else {
out = append(out, st.Field{
Name: prefix + "." + field.Name(),
Type: field.Type().String(),
Start: offsets[i],
End: offsets[i] + size,
Size: size,
Align: s.Alignof(field.Type()),
})
}
pos += size
}
if len(out) == 0 {
return out
}
field := &out[len(out)-1]
if field.Size == 0 {
field.Size = 1
field.End++
}
pad := s.Sizeof(typ) - field.End
if pad > 0 {
out = append(out, st.Field{
IsPadding: true,
Start: field.End,
End: field.End + pad,
Size: pad,
})
}
return out
}

View file

@ -1,20 +0,0 @@
Copyright (c) 2016 Dominik Honnef
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.

View file

@ -70,5 +70,9 @@ func main() {
checker := newChecker(mode)
l := unused.NewLintChecker(checker)
lintutil.ProcessFlagSet(l, fs)
cfg := lintutil.CheckerConfig{
Checker: l,
ExitNonZero: true,
}
lintutil.ProcessFlagSet([]lintutil.CheckerConfig{cfg}, fs)
}

View file

@ -0,0 +1,54 @@
package deprecated
type Deprecation struct {
DeprecatedSince int
AlternativeAvailableSince int
}
var Stdlib = map[string]Deprecation{
"image/jpeg.Reader": {4, 0},
// FIXME(dh): AllowBinary isn't being detected as deprecated
// because the comment has a newline right after "Deprecated:"
"go/build.AllowBinary": {7, 7},
"(archive/zip.FileHeader).CompressedSize": {1, 1},
"(archive/zip.FileHeader).UncompressedSize": {1, 1},
"(go/doc.Package).Bugs": {1, 1},
"os.SEEK_SET": {7, 7},
"os.SEEK_CUR": {7, 7},
"os.SEEK_END": {7, 7},
"(net.Dialer).Cancel": {7, 7},
"runtime.CPUProfile": {9, 0},
"compress/flate.ReadError": {6, 6},
"compress/flate.WriteError": {6, 6},
"path/filepath.HasPrefix": {0, 0},
"(net/http.Transport).Dial": {7, 7},
"(*net/http.Transport).CancelRequest": {6, 5},
"net/http.ErrWriteAfterFlush": {7, 0},
"net/http.ErrHeaderTooLong": {8, 0},
"net/http.ErrShortBody": {8, 0},
"net/http.ErrMissingContentLength": {8, 0},
"net/http/httputil.ErrPersistEOF": {0, 0},
"net/http/httputil.ErrClosed": {0, 0},
"net/http/httputil.ErrPipeline": {0, 0},
"net/http/httputil.ServerConn": {0, 0},
"net/http/httputil.NewServerConn": {0, 0},
"net/http/httputil.ClientConn": {0, 0},
"net/http/httputil.NewClientConn": {0, 0},
"net/http/httputil.NewProxyClientConn": {0, 0},
"(net/http.Request).Cancel": {7, 7},
"(text/template/parse.PipeNode).Line": {1, 1},
"(text/template/parse.ActionNode).Line": {1, 1},
"(text/template/parse.BranchNode).Line": {1, 1},
"(text/template/parse.TemplateNode).Line": {1, 1},
"database/sql/driver.ColumnConverter": {9, 9},
"database/sql/driver.Execer": {8, 8},
"database/sql/driver.Queryer": {8, 8},
"(database/sql/driver.Conn).Begin": {8, 8},
"(database/sql/driver.Stmt).Exec": {8, 8},
"(database/sql/driver.Stmt).Query": {8, 8},
"syscall.StringByteSlice": {1, 1},
"syscall.StringBytePtr": {1, 1},
"syscall.StringSlicePtr": {1, 1},
"syscall.StringToUTF16": {1, 1},
"syscall.StringToUTF16Ptr": {1, 1},
}

View file

@ -0,0 +1,157 @@
package errcheck
import (
"go/types"
"honnef.co/go/tools/functions"
"honnef.co/go/tools/lint"
"honnef.co/go/tools/ssa"
)
type Checker struct {
funcDescs *functions.Descriptions
}
func NewChecker() *Checker {
return &Checker{}
}
func (*Checker) Name() string { return "errcheck" }
func (*Checker) Prefix() string { return "ERR" }
func (c *Checker) Funcs() map[string]lint.Func {
return map[string]lint.Func{
"ERR1000": c.CheckErrcheck,
}
}
func (c *Checker) Init(prog *lint.Program) {
c.funcDescs = functions.NewDescriptions(prog.SSA)
}
func (c *Checker) CheckErrcheck(j *lint.Job) {
for _, ssafn := range j.Program.InitialFunctions {
for _, b := range ssafn.Blocks {
for _, ins := range b.Instrs {
ssacall, ok := ins.(ssa.CallInstruction)
if !ok {
continue
}
switch lint.CallName(ssacall.Common()) {
case "fmt.Print", "fmt.Println", "fmt.Printf":
continue
}
isRecover := false
if builtin, ok := ssacall.Common().Value.(*ssa.Builtin); ok {
isRecover = ok && builtin.Name() == "recover"
}
switch ins := ins.(type) {
case ssa.Value:
refs := ins.Referrers()
if refs == nil || len(lint.FilterDebug(*refs)) != 0 {
continue
}
case ssa.Instruction:
// will be a 'go' or 'defer', neither of which has usable return values
default:
// shouldn't happen
continue
}
if ssacall.Common().IsInvoke() {
if sc, ok := ssacall.Common().Value.(*ssa.Call); ok {
// TODO(dh): support multiple levels of
// interfaces, not just one
ssafn := sc.Common().StaticCallee()
if ssafn != nil {
ct := c.funcDescs.Get(ssafn).ConcreteReturnTypes
// TODO(dh): support >1 concrete types
if ct != nil && len(ct) == 1 {
// TODO(dh): do we have access to a
// cached method set somewhere?
ms := types.NewMethodSet(ct[0].At(ct[0].Len() - 1).Type())
// TODO(dh): where can we get the pkg
// for Lookup? Passing nil works fine
// for exported methods, but will fail
// on unexported ones
// TODO(dh): holy nesting and poor
// variable names, clean this up
fn, _ := ms.Lookup(nil, ssacall.Common().Method.Name()).Obj().(*types.Func)
if fn != nil {
ssafn := j.Program.SSA.FuncValue(fn)
if ssafn != nil {
if c.funcDescs.Get(ssafn).NilError {
continue
}
}
}
}
}
}
} else {
ssafn := ssacall.Common().StaticCallee()
if ssafn != nil {
if c.funcDescs.Get(ssafn).NilError {
// Don't complain when the error is known to be nil
continue
}
}
}
switch lint.CallName(ssacall.Common()) {
case "(*os.File).Close":
recv := ssacall.Common().Args[0]
if isReadOnlyFile(recv, nil) {
continue
}
}
res := ssacall.Common().Signature().Results()
if res.Len() == 0 {
continue
}
if !isRecover {
last := res.At(res.Len() - 1)
if types.TypeString(last.Type(), nil) != "error" {
continue
}
}
j.Errorf(ins, "unchecked error")
}
}
}
}
func isReadOnlyFile(val ssa.Value, seen map[ssa.Value]bool) bool {
if seen == nil {
seen = map[ssa.Value]bool{}
}
if seen[val] {
return true
}
seen[val] = true
switch val := val.(type) {
case *ssa.Phi:
for _, edge := range val.Edges {
if !isReadOnlyFile(edge, seen) {
return false
}
}
return true
case *ssa.Extract:
call, ok := val.Tuple.(*ssa.Call)
if !ok {
return false
}
switch lint.CallName(call.Common()) {
case "os.Open":
return true
case "os.OpenFile":
flags, ok := call.Common().Args[1].(*ssa.Const)
return ok && flags.Uint64() == 0
}
return false
}
return false
}

View file

@ -1,20 +0,0 @@
Copyright (c) 2016 Dominik Honnef
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.

View file

@ -48,6 +48,8 @@ var stdlibDescs = map[string]Description{
type Description struct {
// The function is known to be pure
Pure bool
// The function is known to be a stub
Stub bool
// The function is known to never return (panics notwithstanding)
Infinite bool
// Variable ranges
@ -90,6 +92,7 @@ func (d *Descriptions) Get(fn *ssa.Function) Description {
{
fd.result = stdlibDescs[fn.RelString(nil)]
fd.result.Pure = fd.result.Pure || d.IsPure(fn)
fd.result.Stub = fd.result.Stub || d.IsStub(fn)
fd.result.Infinite = fd.result.Infinite || !terminates(fn)
fd.result.Ranges = vrp.BuildGraph(fn).Solve()
fd.result.Loops = findLoops(fn)

View file

@ -5,9 +5,41 @@ import (
"go/types"
"honnef.co/go/tools/callgraph"
"honnef.co/go/tools/lint"
"honnef.co/go/tools/ssa"
)
// IsStub reports whether a function is a stub. A function is
// considered a stub if it has no instructions or exactly one
// instruction, which must be either returning only constant values or
// a panic.
func (d *Descriptions) IsStub(fn *ssa.Function) bool {
if len(fn.Blocks) == 0 {
return true
}
if len(fn.Blocks) > 1 {
return false
}
instrs := lint.FilterDebug(fn.Blocks[0].Instrs)
if len(instrs) != 1 {
return false
}
switch instrs[0].(type) {
case *ssa.Return:
// Since this is the only instruction, the return value must
// be a constant. We consider all constants as stubs, not just
// the zero value. This does not, unfortunately, cover zero
// initialised structs, as these cause additional
// instructions.
return true
case *ssa.Panic:
return true
default:
return false
}
}
func (d *Descriptions) IsPure(fn *ssa.Function) bool {
if fn.Signature.Results().Len() == 0 {
// A function with no return values is empty or is doing some

View file

@ -1,20 +0,0 @@
Copyright (c) 2016 Dominik Honnef
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.

View file

@ -1,20 +0,0 @@
Copyright (c) 2016 Dominik Honnef
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.

View file

@ -1,20 +0,0 @@
Copyright (c) 2016 Dominik Honnef
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.

View file

@ -11,6 +11,7 @@ import (
"bytes"
"fmt"
"go/ast"
"go/build"
"go/constant"
"go/printer"
"go/token"
@ -20,6 +21,7 @@ import (
"sort"
"strings"
"sync"
"unicode"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/loader"
@ -30,15 +32,85 @@ import (
type Job struct {
Program *Program
checker string
check string
problems []Problem
}
type Ignore struct {
type Ignore interface {
Match(p Problem) bool
}
type LineIgnore struct {
File string
Line int
Checks []string
matched bool
pos token.Pos
}
func (li *LineIgnore) Match(p Problem) bool {
if p.Position.Filename != li.File || p.Position.Line != li.Line {
return false
}
for _, c := range li.Checks {
if m, _ := filepath.Match(c, p.Check); m {
li.matched = true
return true
}
}
return false
}
func (li *LineIgnore) String() string {
matched := "not matched"
if li.matched {
matched = "matched"
}
return fmt.Sprintf("%s:%d %s (%s)", li.File, li.Line, strings.Join(li.Checks, ", "), matched)
}
type FileIgnore struct {
File string
Checks []string
}
func (fi *FileIgnore) Match(p Problem) bool {
if p.Position.Filename != fi.File {
return false
}
for _, c := range fi.Checks {
if m, _ := filepath.Match(c, p.Check); m {
return true
}
}
return false
}
type GlobIgnore struct {
Pattern string
Checks []string
}
func (gi *GlobIgnore) Match(p Problem) bool {
if gi.Pattern != "*" {
pkgpath := p.Package.Path()
if strings.HasSuffix(pkgpath, "_test") {
pkgpath = pkgpath[:len(pkgpath)-len("_test")]
}
name := filepath.Join(pkgpath, filepath.Base(p.Position.Filename))
if m, _ := filepath.Match(gi.Pattern, name); !m {
return false
}
}
for _, c := range gi.Checks {
if m, _ := filepath.Match(c, p.Check); m {
return true
}
}
return false
}
type Program struct {
SSA *ssa.Program
Prog *loader.Program
@ -58,51 +130,70 @@ type Func func(*Job)
// Problem represents a problem in some source code.
type Problem struct {
Position token.Pos // position in source file
Text string // the prose that describes the problem
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
}
func (p *Problem) String() string {
return p.Text
if p.Check == "" {
return p.Text
}
return fmt.Sprintf("%s (%s)", p.Text, p.Check)
}
type Checker interface {
Name() string
Prefix() string
Init(*Program)
Funcs() map[string]Func
}
// A Linter lints Go source code.
type Linter struct {
Checker Checker
Ignores []Ignore
GoVersion int
Checker Checker
Ignores []Ignore
GoVersion int
ReturnIgnored bool
automaticIgnores []Ignore
}
func (l *Linter) ignore(j *Job, p Problem) bool {
tf := j.Program.SSA.Fset.File(p.Position)
f := j.Program.tokenFileMap[tf]
pkg := j.Program.astFileMap[f].Pkg
for _, ig := range l.Ignores {
pkgpath := pkg.Path()
if strings.HasSuffix(pkgpath, "_test") {
pkgpath = pkgpath[:len(pkgpath)-len("_test")]
}
name := filepath.Join(pkgpath, filepath.Base(tf.Name()))
if m, _ := filepath.Match(ig.Pattern, name); !m {
continue
}
for _, c := range ig.Checks {
if m, _ := filepath.Match(c, j.check); m {
return true
}
func (l *Linter) ignore(p Problem) bool {
ignored := false
for _, ig := range l.automaticIgnores {
// We cannot short-circuit these, as we want to record, for
// each ignore, whether it matched or not.
if ig.Match(p) {
ignored = true
}
}
if ignored {
// no need to execute other ignores if we've already had a
// match.
return true
}
for _, ig := range l.Ignores {
// We can short-circuit here, as we aren't tracking any
// information.
if ig.Match(p) {
return true
}
}
return false
}
func (prog *Program) File(node Positioner) *ast.File {
return prog.tokenFileMap[prog.SSA.Fset.File(node.Pos())]
}
func (j *Job) File(node Positioner) *ast.File {
return j.Program.tokenFileMap[j.Program.SSA.Fset.File(node.Pos())]
return j.Program.File(node)
}
// TODO(dh): switch to sort.Slice when Go 1.9 lands.
@ -116,7 +207,7 @@ func (ps byPosition) Len() int {
}
func (ps byPosition) Less(i int, j int) bool {
pi, pj := ps.fset.Position(ps.ps[i].Position), ps.fset.Position(ps.ps[j].Position)
pi, pj := ps.ps[i].Position, ps.ps[j].Position
if pi.Filename != pj.Filename {
return pi.Filename < pj.Filename
@ -135,16 +226,40 @@ func (ps byPosition) Swap(i int, j int) {
ps.ps[i], ps.ps[j] = ps.ps[j], ps.ps[i]
}
func (l *Linter) Lint(lprog *loader.Program) []Problem {
func parseDirective(s string) (cmd string, args []string) {
if !strings.HasPrefix(s, "//lint:") {
return "", nil
}
s = strings.TrimPrefix(s, "//lint:")
fields := strings.Split(s, " ")
return fields[0], fields[1:]
}
func (l *Linter) Lint(lprog *loader.Program, conf *loader.Config) []Problem {
ssaprog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
ssaprog.Build()
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
dir := filepath.Dir(path)
var err error
ctx := conf.Build
if ctx == nil {
ctx = &build.Default
}
bp, err = ctx.ImportDir(dir, 0)
if err != nil {
// shouldn't happen
}
}
pkg := &Pkg{
Package: ssapkg,
Info: pkginfo,
Package: ssapkg,
Info: pkginfo,
BuildPkg: bp,
}
pkgMap[ssapkg] = pkg
pkgs = append(pkgs, pkg)
@ -158,6 +273,7 @@ func (l *Linter) Lint(lprog *loader.Program) []Problem {
tokenFileMap: map[*token.File]*ast.File{},
astFileMap: map[*ast.File]*Pkg{},
}
initial := map[*types.Package]struct{}{}
for _, pkg := range pkgs {
initial[pkg.Info.Pkg] = struct{}{}
@ -176,9 +292,69 @@ func (l *Linter) Lint(lprog *loader.Program) []Problem {
ssapkg := ssaprog.Package(pkg.Info.Pkg)
for _, f := range pkg.Info.Files {
prog.astFileMap[f] = pkgMap[ssapkg]
}
}
for _, pkginfo := range lprog.AllPackages {
for _, f := range pkginfo.Files {
tf := lprog.Fset.File(f.Pos())
prog.tokenFileMap[tf] = f
prog.astFileMap[f] = pkgMap[ssapkg]
}
}
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 node, cgs := range cm {
for _, cg := range cgs {
for _, c := range cg.List {
if !strings.HasPrefix(c.Text, "//lint:") {
continue
}
cmd, args := parseDirective(c.Text)
switch cmd {
case "ignore", "file-ignore":
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(),
Package: nil,
}
out = append(out, p)
continue
}
default:
// unknown directive, ignore
continue
}
checks := strings.Split(args[0], ",")
pos := prog.DisplayPosition(node.Pos())
var ig Ignore
switch cmd {
case "ignore":
ig = &LineIgnore{
File: pos.Filename,
Line: pos.Line,
Checks: checks,
pos: c.Pos(),
}
case "file-ignore":
ig = &FileIgnore{
File: pos.Filename,
Checks: checks,
}
}
l.automaticIgnores = append(l.automaticIgnores, ig)
}
}
}
}
}
@ -237,6 +413,7 @@ func (l *Linter) Lint(lprog *loader.Program) []Problem {
for _, k := range keys {
j := &Job{
Program: prog,
checker: l.Checker.Name(),
check: k,
}
jobs = append(jobs, j)
@ -255,15 +432,47 @@ func (l *Linter) Lint(lprog *loader.Program) []Problem {
}
wg.Wait()
var out []Problem
for _, j := range jobs {
for _, p := range j.problems {
if !l.ignore(j, p) {
p.Ignored = l.ignore(p)
if l.ReturnIgnored || !p.Ignored {
out = append(out, p)
}
}
}
for _, ig := range l.automaticIgnores {
ig, ok := ig.(*LineIgnore)
if !ok {
continue
}
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
continue
}
if c[:idx] != l.Checker.Prefix() {
// not for this checker
continue
}
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)
}
}
sort.Sort(byPosition{lprog.Fset, out})
return out
}
@ -271,7 +480,8 @@ func (l *Linter) Lint(lprog *loader.Program) []Problem {
// Pkg represents a package being linted.
type Pkg struct {
*ssa.Package
Info *loader.PackageInfo
Info *loader.PackageInfo
BuildPkg *build.Package
}
type packager interface {
@ -309,10 +519,55 @@ type Positioner interface {
Pos() token.Pos
}
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
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?)
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
}
}
// not a cgo file, ignore //line directives
return prog.Prog.Fset.PositionFor(p, false)
}
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
pos := j.Program.DisplayPosition(n.Pos())
problem := Problem{
Position: n.Pos(),
Text: fmt.Sprintf(format, args...) + fmt.Sprintf(" (%s)", j.check),
pos: n.Pos(),
Position: pos,
Text: fmt.Sprintf(format, args...),
Check: j.check,
Checker: j.checker,
Package: pkg,
}
j.problems = append(j.problems, problem)
return &j.problems[len(j.problems)-1]
@ -422,6 +677,31 @@ func IsGenerated(f *ast.File) bool {
return false
}
func Preamble(f *ast.File) string {
cutoff := f.Package
if f.Doc != nil {
cutoff = f.Doc.Pos()
}
var out []string
for _, cmt := range f.Comments {
if cmt.Pos() >= cutoff {
break
}
out = append(out, cmt.Text())
}
return strings.Join(out, "\n")
}
func IsPointerLike(T types.Type) bool {
switch T := T.Underlying().(type) {
case *types.Interface, *types.Chan, *types.Map, *types.Pointer:
return true
case *types.Basic:
return T.Kind() == types.UnsafePointer
}
return false
}
func (j *Job) IsGoVersion(minor int) bool {
return j.Program.GoVersion >= minor
}

View file

@ -8,23 +8,70 @@
package lintutil // import "honnef.co/go/tools/lint/lintutil"
import (
"encoding/json"
"errors"
"flag"
"fmt"
"go/build"
"go/parser"
"go/token"
"go/types"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"honnef.co/go/tools/lint"
"honnef.co/go/tools/version"
"github.com/kisielk/gotool"
"golang.org/x/tools/go/loader"
)
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)
@ -38,13 +85,14 @@ func usage(name string, flags *flag.FlagSet) func() {
}
type runner struct {
checker lint.Checker
tags []string
ignores []lint.Ignore
version int
checker lint.Checker
tags []string
ignores []lint.Ignore
version int
returnIgnored bool
}
func (runner runner) resolveRelative(importPaths []string) (goFiles bool, err error) {
func resolveRelative(importPaths []string, tags []string) (goFiles bool, err error) {
if len(importPaths) == 0 {
return false, nil
}
@ -57,7 +105,7 @@ func (runner runner) resolveRelative(importPaths []string) (goFiles bool, err er
return false, err
}
ctx := build.Default
ctx.BuildTags = runner.tags
ctx.BuildTags = tags
for i, path := range importPaths {
bpkg, err := ctx.Import(path, wd, build.FindOnly)
if err != nil {
@ -80,7 +128,7 @@ func parseIgnore(s string) ([]lint.Ignore, error) {
}
path := p[0]
checks := strings.Split(p[1], ",")
out = append(out, lint.Ignore{Pattern: path, Checks: checks})
out = append(out, &lint.GlobIgnore{Pattern: path, Checks: checks})
}
return out, nil
}
@ -117,6 +165,9 @@ func FlagSet(name string) *flag.FlagSet {
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.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')")
tags := build.Default.ReleaseTags
v := tags[len(tags)-1][2:]
@ -129,67 +180,105 @@ func FlagSet(name string) *flag.FlagSet {
return flags
}
func ProcessFlagSet(c lint.Checker, fs *flag.FlagSet) {
type CheckerConfig struct {
Checker lint.Checker
ExitNonZero bool
}
func ProcessFlagSet(confs []CheckerConfig, 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)
version := fs.Lookup("go").Value.(flag.Getter).Get().(int)
goVersion := fs.Lookup("go").Value.(flag.Getter).Get().(int)
format := 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)
ps, lprog, err := Lint(c, fs.Args(), &Options{
Tags: strings.Fields(tags),
LintTests: tests,
Ignores: ignore,
GoVersion: version,
if printVersion {
version.Print()
os.Exit(0)
}
var cs []lint.Checker
for _, conf := range confs {
cs = append(cs, conf.Checker)
}
pss, err := Lint(cs, fs.Args(), &Options{
Tags: strings.Fields(tags),
LintTests: tests,
Ignores: ignore,
GoVersion: goVersion,
ReturnIgnored: showIgnored,
})
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
unclean := false
for _, p := range ps {
unclean = true
pos := lprog.Fset.Position(p.Position)
fmt.Printf("%v: %s\n", relativePositionString(pos), p.Text)
var ps []lint.Problem
for _, p := range pss {
ps = append(ps, p...)
}
if unclean {
os.Exit(1)
var f OutputFormatter
switch format {
case "text":
f = TextOutput{os.Stdout}
case "json":
f = JSONOutput{os.Stdout}
default:
fmt.Fprintf(os.Stderr, "unsupported output format %q\n", format)
os.Exit(2)
}
for _, p := range ps {
f.Format(p)
}
for i, p := range pss {
if len(p) != 0 && confs[i].ExitNonZero {
os.Exit(1)
}
}
}
type Options struct {
Tags []string
LintTests bool
Ignores string
GoVersion int
Tags []string
LintTests bool
Ignores string
GoVersion int
ReturnIgnored bool
}
func Lint(c lint.Checker, pkgs []string, opt *Options) ([]lint.Problem, *loader.Program, error) {
// TODO(dh): Instead of returning the loader.Program, we should
// store token.Position instead of token.Pos in lint.Problem.
func Lint(cs []lint.Checker, pkgs []string, opt *Options) ([][]lint.Problem, error) {
if opt == nil {
opt = &Options{}
}
ignores, err := parseIgnore(opt.Ignores)
if err != nil {
return nil, nil, err
}
runner := &runner{
checker: c,
tags: opt.Tags,
ignores: ignores,
version: opt.GoVersion,
return nil, err
}
paths := gotool.ImportPaths(pkgs)
goFiles, err := runner.resolveRelative(paths)
goFiles, err := resolveRelative(paths, opt.Tags)
if err != nil {
return nil, nil, err
return nil, err
}
ctx := build.Default
ctx.BuildTags = runner.tags
ctx.BuildTags = opt.Tags
hadError := false
conf := &loader.Config{
Build: &ctx,
ParserMode: parser.ParseComments,
ImportPkgs: map[string]bool{},
TypeChecker: types.Config{
Error: func(err error) {
// Only print the first error found
if hadError {
return
}
hadError = true
fmt.Fprintln(os.Stderr, err)
},
},
}
if goFiles {
conf.CreateFromFilenames("adhoc", paths...)
@ -200,9 +289,21 @@ func Lint(c lint.Checker, pkgs []string, opt *Options) ([]lint.Problem, *loader.
}
lprog, err := conf.Load()
if err != nil {
return nil, nil, err
return nil, err
}
return runner.lint(lprog), lprog, nil
var problems [][]lint.Problem
for _, c := range cs {
runner := &runner{
checker: c,
tags: opt.Tags,
ignores: ignores,
version: opt.GoVersion,
returnIgnored: opt.ReturnIgnored,
}
problems = append(problems, runner.lint(lprog, conf))
}
return problems, nil
}
func shortPath(path string) string {
@ -230,18 +331,19 @@ func relativePositionString(pos token.Position) string {
return s
}
func ProcessArgs(name string, c lint.Checker, args []string) {
func ProcessArgs(name string, cs []CheckerConfig, args []string) {
flags := FlagSet(name)
flags.Parse(args)
ProcessFlagSet(c, flags)
ProcessFlagSet(cs, flags)
}
func (runner *runner) lint(lprog *loader.Program) []lint.Problem {
func (runner *runner) lint(lprog *loader.Program, conf *loader.Config) []lint.Problem {
l := &lint.Linter{
Checker: runner.checker,
Ignores: runner.ignores,
GoVersion: runner.version,
Checker: runner.checker,
Ignores: runner.ignores,
GoVersion: runner.version,
ReturnIgnored: runner.returnIgnored,
}
return l.Lint(lprog)
return l.Lint(lprog, conf)
}

View file

@ -91,7 +91,7 @@ func TestAll(t *testing.T, c lint.Checker, dir string) {
for version, fis := range files {
l := &lint.Linter{Checker: c, GoVersion: version}
res := l.Lint(lprog)
res := l.Lint(lprog, conf)
for _, fi := range fis {
name := fi.Name()
src := sources[name]
@ -101,8 +101,7 @@ func TestAll(t *testing.T, c lint.Checker, dir string) {
for _, in := range ins {
ok := false
for i, p := range res {
pos := lprog.Fset.Position(p.Position)
if pos.Line != in.Line || filepath.Base(pos.Filename) != name {
if p.Position.Line != in.Line || filepath.Base(p.Position.Filename) != name {
continue
}
if in.Match.MatchString(p.Text) {
@ -121,11 +120,10 @@ func TestAll(t *testing.T, c lint.Checker, dir string) {
}
}
for _, p := range res {
pos := lprog.Fset.Position(p.Position)
name := filepath.Base(pos.Filename)
name := filepath.Base(p.Position.Filename)
for _, fi := range fis {
if name == fi.Name() {
t.Errorf("Unexpected problem at %s: %v", pos, p.Text)
t.Errorf("Unexpected problem at %s: %v", p.Position, p.Text)
break
}
}
@ -149,7 +147,7 @@ func parseInstructions(t *testing.T, filename string, src []byte) []instruction
}
var ins []instruction
for _, cg := range f.Comments {
ln := fset.Position(cg.Pos()).Line
ln := fset.PositionFor(cg.Pos(), false).Line
raw := cg.Text()
for _, line := range strings.Split(raw, "\n") {
if line == "" || strings.HasPrefix(line, "#") {

View file

@ -1,20 +0,0 @@
Copyright (c) 2016 Dominik Honnef
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.

View file

@ -29,6 +29,9 @@ func NewChecker() *Checker {
}
}
func (*Checker) Name() string { return "gosimple" }
func (*Checker) Prefix() string { return "S" }
func (c *Checker) Init(prog *lint.Program) {
c.nodeFns = lint.NodeFns(prog.Packages)
}
@ -61,7 +64,7 @@ func (c *Checker) Funcs() map[string]lint.Func {
"S1023": c.LintRedundantBreak,
"S1024": c.LintTimeUntil,
"S1025": c.LintRedundantSprintf,
"S1026": c.LintStringCopy,
"S1026": nil,
"S1027": nil,
"S1028": c.LintErrorsNewSprintf,
"S1029": c.LintRangeStringRunes,
@ -1022,7 +1025,9 @@ func (c *Checker) LintUnnecessaryBlank(j *lint.Job) {
fn := func(node ast.Node) bool {
fn1(node)
fn2(node)
fn3(node)
if j.IsGoVersion(4) {
fn3(node)
}
return true
}
for _, f := range c.filterGenerated(j.Program.Files) {
@ -1702,81 +1707,6 @@ func (c *Checker) LintRedundantSprintf(j *lint.Job) {
}
}
func (c *Checker) LintStringCopy(j *lint.Job) {
emptyStringLit := func(e ast.Expr) bool {
bl, ok := e.(*ast.BasicLit)
return ok && bl.Value == `""`
}
fn := func(node ast.Node) bool {
switch x := node.(type) {
case *ast.BinaryExpr: // "" + s, s + ""
if x.Op != token.ADD {
break
}
l1 := j.Program.Prog.Fset.Position(x.X.Pos()).Line
l2 := j.Program.Prog.Fset.Position(x.Y.Pos()).Line
if l1 != l2 {
break
}
var want ast.Expr
switch {
case emptyStringLit(x.X):
want = x.Y
case emptyStringLit(x.Y):
want = x.X
default:
return true
}
j.Errorf(x, "should use %s instead of %s",
j.Render(want), j.Render(x))
case *ast.CallExpr:
if j.IsCallToAST(x, "fmt.Sprint") && len(x.Args) == 1 {
// fmt.Sprint(x)
argT := j.Program.Info.TypeOf(x.Args[0])
bt, ok := argT.Underlying().(*types.Basic)
if !ok || bt.Kind() != types.String {
return true
}
if c.Implements(j, argT, "fmt.Stringer") || c.Implements(j, argT, "error") {
return true
}
j.Errorf(x, "should use %s instead of %s", j.Render(x.Args[0]), j.Render(x))
return true
}
// string([]byte(s))
bt, ok := j.Program.Info.TypeOf(x.Fun).(*types.Basic)
if !ok || bt.Kind() != types.String {
break
}
nested, ok := x.Args[0].(*ast.CallExpr)
if !ok {
break
}
st, ok := j.Program.Info.TypeOf(nested.Fun).(*types.Slice)
if !ok {
break
}
et, ok := st.Elem().(*types.Basic)
if !ok || et.Kind() != types.Byte {
break
}
xt, ok := j.Program.Info.TypeOf(nested.Args[0]).(*types.Basic)
if !ok || xt.Kind() != types.String {
break
}
j.Errorf(x, "should use %s instead of %s",
j.Render(nested.Args[0]), j.Render(x))
}
return true
}
for _, f := range c.filterGenerated(j.Program.Files) {
ast.Inspect(f, fn)
}
}
func (c *Checker) LintErrorsNewSprintf(j *lint.Job) {
fn := func(node ast.Node) bool {
if !j.IsCallToAST(node, "errors.New") {

View file

@ -1,20 +0,0 @@
Copyright (c) 2016 Dominik Honnef
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.

View file

@ -1,20 +0,0 @@
Copyright (c) 2016 Dominik Honnef
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.

View file

@ -0,0 +1,21 @@
package staticcheck
import (
"go/ast"
"strings"
"honnef.co/go/tools/lint"
)
func buildTags(f *ast.File) [][]string {
var out [][]string
for _, line := range strings.Split(lint.Preamble(f), "\n") {
if !strings.HasPrefix(line, "+build ") {
continue
}
line = strings.TrimSpace(strings.TrimPrefix(line, "+build "))
fields := strings.Fields(line)
out = append(out, fields)
}
return out
}

View file

@ -4,20 +4,20 @@ package staticcheck // import "honnef.co/go/tools/staticcheck"
import (
"fmt"
"go/ast"
"go/build"
"go/constant"
"go/token"
"go/types"
htmltemplate "html/template"
"net/http"
"regexp"
"sort"
"strconv"
"strings"
"sync"
texttemplate "text/template"
"honnef.co/go/tools/deprecated"
"honnef.co/go/tools/functions"
"honnef.co/go/tools/gcsizes"
"honnef.co/go/tools/internal/sharedcheck"
"honnef.co/go/tools/lint"
"honnef.co/go/tools/ssa"
@ -111,14 +111,12 @@ var (
},
}
checkSyncPoolSizeRules = map[string]CallCheck{
checkSyncPoolValueRules = map[string]CallCheck{
"(*sync.Pool).Put": func(call *Call) {
// TODO(dh): allow users to pass in a custom build environment
sizes := gcsizes.ForArch(build.Default.GOARCH)
arg := call.Args[0]
typ := arg.Value.Value.Type()
if !types.IsInterface(typ) && sizes.Sizeof(typ) > sizes.WordSize {
arg.Invalid("argument should be one word large or less to avoid allocations")
if !lint.IsPointerLike(typ) {
arg.Invalid("argument should be pointer-like to avoid allocations")
}
},
}
@ -209,6 +207,9 @@ func NewChecker() *Checker {
return &Checker{}
}
func (*Checker) Name() string { return "staticcheck" }
func (*Checker) Prefix() string { return "SA" }
func (c *Checker) Funcs() map[string]lint.Func {
return map[string]lint.Func{
"SA1000": c.callChecker(checkRegexpRules),
@ -265,6 +266,7 @@ func (c *Checker) Funcs() map[string]lint.Func {
"SA4016": c.CheckSillyBitwiseOps,
"SA4017": c.CheckPureFunctions,
"SA4018": c.CheckSelfAssignment,
"SA4019": c.CheckDuplicateBuildConstraints,
"SA5000": c.CheckNilMaps,
"SA5001": c.CheckEarlyDefer,
@ -277,7 +279,7 @@ func (c *Checker) Funcs() map[string]lint.Func {
"SA6000": c.callChecker(checkRegexpMatchLoopRules),
"SA6001": c.CheckMapBytesKey,
"SA6002": c.callChecker(checkSyncPoolSizeRules),
"SA6002": c.callChecker(checkSyncPoolValueRules),
"SA6003": c.CheckRangeStringRunes,
"SA6004": nil,
@ -301,36 +303,62 @@ func (c *Checker) filterGenerated(files []*ast.File) []*ast.File {
return out
}
func (c *Checker) Init(prog *lint.Program) {
c.funcDescs = functions.NewDescriptions(prog.SSA)
c.deprecatedObjs = map[types.Object]string{}
c.nodeFns = map[ast.Node]*ssa.Function{}
for _, fn := range prog.AllFunctions {
if fn.Blocks != nil {
applyStdlibKnowledge(fn)
ssa.OptimizeBlocks(fn)
}
func (c *Checker) deprecateObject(m map[types.Object]string, prog *lint.Program, obj types.Object) {
if obj.Pkg() == nil {
return
}
c.nodeFns = lint.NodeFns(prog.Packages)
f := prog.File(obj)
if f == nil {
return
}
msg := c.deprecationMessage(f, prog.Prog.Fset, obj)
if msg != "" {
m[obj] = msg
}
}
deprecated := []map[types.Object]string{}
func (c *Checker) Init(prog *lint.Program) {
wg := &sync.WaitGroup{}
for _, pkginfo := range prog.Prog.AllPackages {
pkginfo := pkginfo
scope := pkginfo.Pkg.Scope()
names := scope.Names()
wg.Add(1)
wg.Add(3)
go func() {
c.funcDescs = functions.NewDescriptions(prog.SSA)
for _, fn := range prog.AllFunctions {
if fn.Blocks != nil {
applyStdlibKnowledge(fn)
ssa.OptimizeBlocks(fn)
}
}
wg.Done()
}()
m := map[types.Object]string{}
deprecated = append(deprecated, m)
go func(m map[types.Object]string) {
for _, name := range names {
obj := scope.Lookup(name)
msg := c.deprecationMessage(pkginfo.Files, prog.SSA.Fset, obj)
if msg != "" {
m[obj] = msg
go func() {
c.nodeFns = lint.NodeFns(prog.Packages)
wg.Done()
}()
go func() {
c.deprecatedObjs = map[types.Object]string{}
for _, ssapkg := range prog.SSA.AllPackages() {
ssapkg := ssapkg
for _, member := range ssapkg.Members {
obj := member.Object()
if obj == nil {
continue
}
c.deprecateObject(c.deprecatedObjs, prog, obj)
if typ, ok := obj.Type().(*types.Named); ok {
for i := 0; i < typ.NumMethods(); i++ {
meth := typ.Method(i)
c.deprecateObject(c.deprecatedObjs, prog, meth)
}
if iface, ok := typ.Underlying().(*types.Interface); ok {
for i := 0; i < iface.NumExplicitMethods(); i++ {
meth := iface.ExplicitMethod(i)
c.deprecateObject(c.deprecatedObjs, prog, meth)
}
}
}
if typ, ok := obj.Type().Underlying().(*types.Struct); ok {
n := typ.NumFields()
@ -338,51 +366,20 @@ func (c *Checker) Init(prog *lint.Program) {
// FIXME(dh): This code will not find deprecated
// fields in anonymous structs.
field := typ.Field(i)
msg := c.deprecationMessage(pkginfo.Files, prog.SSA.Fset, field)
if msg != "" {
m[field] = msg
}
c.deprecateObject(c.deprecatedObjs, prog, field)
}
}
}
wg.Done()
}(m)
}
}
wg.Done()
}()
wg.Wait()
for _, m := range deprecated {
for k, v := range m {
c.deprecatedObjs[k] = v
}
}
}
// TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos)
func tokenFileContainsPos(f *token.File, pos token.Pos) bool {
p := int(pos)
base := f.Base()
return base <= p && p < base+f.Size()
}
func pathEnclosingInterval(files []*ast.File, fset *token.FileSet, start, end token.Pos) (path []ast.Node, exact bool) {
for _, f := range files {
if f.Pos() == token.NoPos {
// This can happen if the parser saw
// too many errors and bailed out.
// (Use parser.AllErrors to prevent that.)
continue
}
if !tokenFileContainsPos(fset.File(f.Pos()), start) {
continue
}
if path, exact := astutil.PathEnclosingInterval(f, start, end); path != nil {
return path, exact
}
}
return nil, false
}
func (c *Checker) deprecationMessage(files []*ast.File, fset *token.FileSet, obj types.Object) (message string) {
path, _ := pathEnclosingInterval(files, fset, obj.Pos(), obj.Pos())
func (c *Checker) deprecationMessage(file *ast.File, fset *token.FileSet, obj types.Object) (message string) {
pos := obj.Pos()
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
if len(path) <= 2 {
return ""
}
@ -2065,7 +2062,7 @@ func (c *Checker) CheckCyclicFinalizer(j *lint.Job) {
}
for _, b := range mc.Bindings {
if b == v {
pos := j.Program.SSA.Fset.Position(mc.Fn.Pos())
pos := j.Program.DisplayPosition(mc.Fn.Pos())
j.Errorf(edge.Site, "the finalizer closes over the object, preventing the finalizer from ever running (at %s)", pos)
}
}
@ -2166,6 +2163,11 @@ func (c *Checker) CheckInfiniteRecursion(j *lint.Job) {
if edge.Callee != node {
continue
}
if _, ok := edge.Site.(*ssa.Go); ok {
// Recursively spawning goroutines doesn't consume
// stack space infinitely, so don't flag it.
continue
}
block := edge.Site.Block()
canReturn := false
@ -2437,7 +2439,7 @@ fnLoop:
if callee == nil {
continue
}
if c.funcDescs.Get(callee).Pure {
if c.funcDescs.Get(callee).Pure && !c.funcDescs.Get(callee).Stub {
j.Errorf(ins, "%s is a pure function but its return value is ignored", callee.Name())
continue
}
@ -2446,22 +2448,6 @@ fnLoop:
}
}
func enclosingFunction(j *lint.Job, node ast.Node) *ast.FuncDecl {
f := j.File(node)
path, _ := astutil.PathEnclosingInterval(f, node.Pos(), node.Pos())
for _, e := range path {
fn, ok := e.(*ast.FuncDecl)
if !ok {
continue
}
if fn.Name == nil {
continue
}
return fn
}
return nil
}
func (c *Checker) isDeprecated(j *lint.Job, ident *ast.Ident) (bool, string) {
obj := j.Program.Info.ObjectOf(ident)
if obj.Pkg() == nil {
@ -2471,19 +2457,34 @@ func (c *Checker) isDeprecated(j *lint.Job, ident *ast.Ident) (bool, string) {
return alt != "", alt
}
func selectorName(j *lint.Job, expr *ast.SelectorExpr) string {
sel := j.Program.Info.Selections[expr]
if sel == nil {
if x, ok := expr.X.(*ast.Ident); ok {
return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name)
}
panic(fmt.Sprintf("unsupported selector: %v", expr))
}
return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name())
}
func (c *Checker) enclosingFunc(sel *ast.SelectorExpr) *ssa.Function {
fn := c.nodeFns[sel]
if fn == nil {
return nil
}
for fn.Parent() != nil {
fn = fn.Parent()
}
return fn
}
func (c *Checker) CheckDeprecated(j *lint.Job) {
fn := func(node ast.Node) bool {
sel, ok := node.(*ast.SelectorExpr)
if !ok {
return true
}
if fn := enclosingFunction(j, sel); fn != nil {
if ok, _ := c.isDeprecated(j, fn.Name); ok {
// functions that are deprecated may use deprecated
// symbols
return true
}
}
obj := j.Program.Info.ObjectOf(sel.Sel)
if obj.Pkg() == nil {
@ -2495,6 +2496,24 @@ func (c *Checker) CheckDeprecated(j *lint.Job) {
return true
}
if ok, alt := c.isDeprecated(j, sel.Sel); ok {
// Look for the first available alternative, not the first
// version something was deprecated in. If a function was
// deprecated in Go 1.6, an alternative has been available
// already in 1.0, and we're targetting 1.2, it still
// makes sense to use the alternative from 1.0, to be
// future-proof.
minVersion := deprecated.Stdlib[selectorName(j, sel)].AlternativeAvailableSince
if !j.IsGoVersion(minVersion) {
return true
}
if fn := c.enclosingFunc(sel); fn != nil {
if _, ok := c.deprecatedObjs[fn.Object()]; ok {
// functions that are deprecated may use deprecated
// symbols
return true
}
}
j.Errorf(sel, "%s is deprecated: %s", j.Render(sel), alt)
return true
}
@ -2784,3 +2803,39 @@ func (c *Checker) CheckSelfAssignment(j *lint.Job) {
ast.Inspect(f, fn)
}
}
func buildTagsIdentical(s1, s2 []string) bool {
if len(s1) != len(s2) {
return false
}
s1s := make([]string, len(s1))
copy(s1s, s1)
sort.Strings(s1s)
s2s := make([]string, len(s2))
copy(s2s, s2)
sort.Strings(s2s)
for i, s := range s1s {
if s != s2s[i] {
return false
}
}
return true
}
func (c *Checker) CheckDuplicateBuildConstraints(job *lint.Job) {
for _, f := range c.filterGenerated(job.Program.Files) {
constraints := buildTags(f)
for i, constraint1 := range constraints {
for j, constraint2 := range constraints {
if i >= j {
continue
}
if buildTagsIdentical(constraint1, constraint2) {
job.Errorf(f, "identical build constraints %q and %q",
strings.Join(constraint1, " "),
strings.Join(constraint2, " "))
}
}
}
}
}

View file

@ -0,0 +1,22 @@
package structlayout
import "fmt"
type Field struct {
Name string `json:"name"`
Type string `json:"type"`
Start int64 `json:"start"`
End int64 `json:"end"`
Size int64 `json:"size"`
Align int64 `json:"align"`
IsPadding bool `json:"is_padding"`
}
func (f Field) String() string {
if f.IsPadding {
return fmt.Sprintf("%s: %d-%d (size %d, align %d)",
"padding", f.Start, f.End, f.Size, f.Align)
}
return fmt.Sprintf("%s %s: %d-%d (size %d, align %d)",
f.Name, f.Type, f.Start, f.End, f.Size, f.Align)
}

View file

@ -1,20 +0,0 @@
Copyright (c) 2016 Dominik Honnef
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.

View file

@ -26,6 +26,9 @@ type LintChecker struct {
c *Checker
}
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{
@ -275,6 +278,51 @@ func (c *Checker) Check(lprog *loader.Program) []Unused {
return unused
}
// isNoCopyType reports whether a type represents the NoCopy sentinel
// type. The NoCopy type is a named struct with no fields and exactly
// one method `func Lock()` that is empty.
//
// FIXME(dh): currently we're not checking that the function body is
// empty.
func isNoCopyType(typ types.Type) bool {
st, ok := typ.Underlying().(*types.Struct)
if !ok {
return false
}
if st.NumFields() != 0 {
return false
}
named, ok := typ.(*types.Named)
if !ok {
return false
}
if named.NumMethods() != 1 {
return false
}
meth := named.Method(0)
if meth.Name() != "Lock" {
return false
}
sig := meth.Type().(*types.Signature)
if sig.Params().Len() != 0 || sig.Results().Len() != 0 {
return false
}
return true
}
func (c *Checker) useNoCopyFields(typ types.Type) {
if st, ok := typ.Underlying().(*types.Struct); ok {
n := st.NumFields()
for i := 0; i < n; i++ {
field := st.Field(i)
if isNoCopyType(field.Type()) {
c.graph.markUsedBy(field, typ)
}
}
}
}
func (c *Checker) useExportedFields(typ types.Type) {
if st, ok := typ.Underlying().(*types.Struct); ok {
n := st.NumFields()
@ -485,6 +533,7 @@ func (c *Checker) processTypes(pkg *loader.PackageInfo) {
interfaces = append(interfaces, obj)
}
case *types.Struct:
c.useNoCopyFields(obj)
if pkg.Pkg.Name() != "main" && !c.WholeProgram {
c.useExportedFields(obj)
}

View file

@ -0,0 +1,17 @@
package version
import (
"fmt"
"os"
"path/filepath"
)
const Version = "2017.2"
func Print() {
if Version == "devel" {
fmt.Printf("%s (no version)\n", filepath.Base(os.Args[0]))
} else {
fmt.Printf("%s %s\n", filepath.Base(os.Args[0]), Version)
}
}

View file

@ -309,120 +309,20 @@
"notests": true
},
{
"importpath": "honnef.co/go/tools/callgraph",
"repository": "https://github.com/dominikh/go-tools",
"importpath": "golang.org/x/tools/refactor/importgraph",
"repository": "https://go.googlesource.com/tools",
"vcs": "git",
"revision": "49f44f893d933fd08cd7d67d65ccefa5d7c23329",
"revision": "9c477bae194915bfd4bc8c314e90e28b9ec1c831",
"branch": "master",
"path": "callgraph",
"path": "/refactor/importgraph",
"notests": true
},
{
"importpath": "honnef.co/go/tools/cmd/gosimple",
"importpath": "honnef.co/go/tools",
"repository": "https://github.com/dominikh/go-tools",
"vcs": "git",
"revision": "49f44f893d933fd08cd7d67d65ccefa5d7c23329",
"branch": "master",
"path": "/cmd/gosimple",
"notests": true
},
{
"importpath": "honnef.co/go/tools/cmd/megacheck",
"repository": "https://github.com/dominikh/go-tools",
"vcs": "git",
"revision": "49f44f893d933fd08cd7d67d65ccefa5d7c23329",
"branch": "master",
"path": "/cmd/megacheck",
"notests": true
},
{
"importpath": "honnef.co/go/tools/cmd/staticcheck",
"repository": "https://github.com/dominikh/go-tools",
"vcs": "git",
"revision": "49f44f893d933fd08cd7d67d65ccefa5d7c23329",
"branch": "master",
"path": "/cmd/staticcheck",
"notests": true
},
{
"importpath": "honnef.co/go/tools/cmd/unused",
"repository": "https://github.com/dominikh/go-tools",
"vcs": "git",
"revision": "49f44f893d933fd08cd7d67d65ccefa5d7c23329",
"branch": "master",
"path": "/cmd/unused",
"notests": true
},
{
"importpath": "honnef.co/go/tools/functions",
"repository": "https://github.com/dominikh/go-tools",
"vcs": "git",
"revision": "49f44f893d933fd08cd7d67d65ccefa5d7c23329",
"branch": "master",
"path": "functions",
"notests": true
},
{
"importpath": "honnef.co/go/tools/gcsizes",
"repository": "https://github.com/dominikh/go-tools",
"vcs": "git",
"revision": "49f44f893d933fd08cd7d67d65ccefa5d7c23329",
"branch": "master",
"path": "gcsizes",
"notests": true
},
{
"importpath": "honnef.co/go/tools/internal/sharedcheck",
"repository": "https://github.com/dominikh/go-tools",
"vcs": "git",
"revision": "49f44f893d933fd08cd7d67d65ccefa5d7c23329",
"branch": "master",
"path": "/internal/sharedcheck",
"notests": true
},
{
"importpath": "honnef.co/go/tools/lint",
"repository": "https://github.com/dominikh/go-tools",
"vcs": "git",
"revision": "49f44f893d933fd08cd7d67d65ccefa5d7c23329",
"branch": "master",
"path": "lint",
"notests": true
},
{
"importpath": "honnef.co/go/tools/simple",
"repository": "https://github.com/dominikh/go-tools",
"vcs": "git",
"revision": "49f44f893d933fd08cd7d67d65ccefa5d7c23329",
"branch": "master",
"path": "simple",
"notests": true
},
{
"importpath": "honnef.co/go/tools/ssa",
"repository": "https://github.com/dominikh/go-tools",
"vcs": "git",
"revision": "49f44f893d933fd08cd7d67d65ccefa5d7c23329",
"branch": "master",
"path": "ssa",
"notests": true
},
{
"importpath": "honnef.co/go/tools/staticcheck",
"repository": "https://github.com/dominikh/go-tools",
"vcs": "git",
"revision": "49f44f893d933fd08cd7d67d65ccefa5d7c23329",
"branch": "master",
"path": "staticcheck",
"notests": true
},
{
"importpath": "honnef.co/go/tools/unused",
"repository": "https://github.com/dominikh/go-tools",
"vcs": "git",
"revision": "49f44f893d933fd08cd7d67d65ccefa5d7c23329",
"branch": "master",
"path": "unused",
"revision": "50914165a1ae448f1608c6c325d052313396182e",
"branch": "HEAD",
"notests": true
},
{
@ -450,4 +350,4 @@
"notests": true
}
]
}
}

View file

@ -235,7 +235,7 @@ var defaultLinters = map[string]LinterConfig{
Command: `gas -fmt=csv`,
Pattern: `^(?P<path>.*?\.go),(?P<line>\d+),(?P<message>[^,]+,[^,]+,[^,]+)`,
InstallFrom: "github.com/GoASTScanner/gas",
PartitionStrategy: partitionPathsAsDirectories,
PartitionStrategy: partitionPathsAsFiles,
defaultEnabled: true,
IsFast: true,
},

View file

@ -26,7 +26,7 @@ var (
)
func setupFlags(app *kingpin.Application) {
app.Flag("config", "Load JSON configuration from file.").Action(loadConfig).String()
app.Flag("config", "Load JSON configuration from file.").Envar("GOMETALINTER_CONFIG").Action(loadConfig).String()
app.Flag("disable", "Disable previously enabled linters.").PlaceHolder("LINTER").Short('D').Action(disableAction).Strings()
app.Flag("enable", "Enable previously disabled linters.").PlaceHolder("LINTER").Short('E').Action(enableAction).Strings()
app.Flag("linter", "Define a linter.").PlaceHolder("NAME:COMMAND:PATTERN").Action(cliLinterOverrides).StringMap()
@ -156,8 +156,8 @@ func formatLinters() string {
if install == "()" {
install = ""
}
fmt.Fprintf(w, " %s %s\n %s\n %s\n",
linter.Name, install, linter.Command, linter.Pattern)
fmt.Fprintf(w, " %s: %s\n\tcommand: %s\n\tregex: %s\n\tfast: %t\n\tdefault enabled: %t\n\n",
linter.Name, install, linter.Command, linter.Pattern, linter.IsFast, linter.defaultEnabled)
}
return w.String()
}

View file

@ -0,0 +1,37 @@
package regressiontests
import (
"fmt"
"testing"
"github.com/gotestyourself/gotestyourself/fs"
"github.com/stretchr/testify/assert"
)
func TestGas(t *testing.T) {
t.Parallel()
dir := fs.NewDir(t, "test-gas",
fs.WithFile("file.go", gasFileErrorUnhandled("root")),
fs.WithDir("sub",
fs.WithFile("file.go", gasFileErrorUnhandled("sub"))))
defer dir.Remove()
expected := Issues{
{Linter: "gas", Severity: "warning", Path: "file.go", Line: 3, Col: 0, Message: "Errors unhandled.,LOW,HIGH"},
{Linter: "gas", Severity: "warning", Path: "sub/file.go", Line: 3, Col: 0, Message: "Errors unhandled.,LOW,HIGH"},
}
actual := RunLinter(t, "gas", dir.Path())
assert.Equal(t, expected, actual)
}
func gasFileErrorUnhandled(pkg string) string {
return fmt.Sprintf(`package %s
func badFunction() string {
u, _ := ErrorHandle()
return u
}
func ErrorHandle() (u string, err error) {
return u
}
`, pkg)
}