update go-critic

$ git cherry --abbrev -v 0af0999fabfb ee9bf5809ead
+ abd8436 all: enable Go modules on CI ()
+ 3c9d0fb checkers: recognize //line and //nolint in commentFormatting ()
+ 0b517d7 checkers: extend deprecatedComment patterns ()
+ 09100f6 checkers: use astcast package instead of coerce.go ()
+ 2e9e97f checker: simplify boolExprSimplify ()
+ 575701e make: add go-consistent to CI checks list ()
+ b55f431 checkers: fix unlambda handling of builtins ()
+ 5a7dee3 checker: handle lambdas properly in boolExprSimplify ()
+ 5ce3939 checkers: teach boolExprSimplify a few new tricks ()
+ 04d160f checkers: add new patterns to boolExprSimplify ()
+ 09582e2 make: collect coverprofile separately from goveralls ()
+ d8d0ee4 checkers: recognize NOTE pattern in deprecatedComment ()
+ 12f0f85 Update copyright notice to 2019 ()
+ f54bdb6 checkers: add stringXbytes checker
+ 170d65c checkers: followup for  ()
+ 84e9e83 checkers: make stringXbytes more linear ()
+ a800815 checkers: add Depreacted typo pattern ()
+ 6751be9 checkers: add hexLiterals ()
+ ac61906 checkers: add typeAssertChain checker ()
+ d19dbf1 checkers: add codegenComment checker ()
+ d82b576 checkers: proper pkg/obj check for flagName ()
+ dfcf754 ci: enable integration tests ()
+ 5dafc45 checkers: fix equalFold false positive ()
+ ed5e8e7 checkers: refactor and fix hexLiteral checker ()
+ e704e07 checkers: add argOrder checker ()
+ 34c1dc8 checkers: add Split handling to argOrder checker ()
+ cbe095d checkers: add math.Max and math.Min to dupArg ()
+ c986ee5 checkers: add checkers info fields test ()
+ 66e5832 cmd/makedocs: use lintpack, fix build ()
+ 6bce9d0 cmd/makedocs: add enabled/disabled by default info ()
+ 4adbf9a checkers: simplify flagName ()
+ 07de34a checkers: add octalLiteral checker ()
+ 765907a cmd/makedocs: add checker param docs ()
+ ee9bf58 cmd/makedocs: fix headers formatting ()
This commit is contained in:
Denis Isaev 2019-02-11 09:33:43 +03:00
parent b3bad285d0
commit b31cfd6c78
No known key found for this signature in database
GPG Key ID: A36A0EC8E27A1A01
26 changed files with 1492 additions and 213 deletions

@ -31,7 +31,6 @@ linters-settings:
- experimental
disabled-checks:
- wrapperFunc
- commentFormatting # https://github.com/go-critic/go-critic/issues/755
linters:
enable-all: true

@ -779,7 +779,6 @@ linters-settings:
- experimental
disabled-checks:
- wrapperFunc
- commentFormatting # https://github.com/go-critic/go-critic/issues/755
linters:
enable-all: true

2
go.mod

@ -6,7 +6,7 @@ require (
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/fatih/color v1.6.0
github.com/go-critic/go-critic v0.0.0-20181204210945-0af0999fabfb
github.com/go-critic/go-critic v0.0.0-20181204210945-ee9bf5809ead
github.com/go-lintpack/lintpack v0.5.2
github.com/go-ole/go-ole v1.2.1 // indirect
github.com/gobwas/glob v0.2.3 // indirect

5
go.sum

@ -10,8 +10,8 @@ github.com/fatih/color v1.6.0 h1:66qjqZk8kalYAvDRtM1AdAJQI0tj4Wrue3Eq3B3pmFU=
github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-critic/go-critic v0.0.0-20181204210945-0af0999fabfb h1:faOtDYqSVJsFEJAW+SwEMvh7alhYsb42fER6tt8yXfA=
github.com/go-critic/go-critic v0.0.0-20181204210945-0af0999fabfb/go.mod h1:PSww+HOJZQ3TN2hi6sphNiW1PhwELxbsK8+Jy1sjML8=
github.com/go-critic/go-critic v0.0.0-20181204210945-ee9bf5809ead h1:qwmAYufKDopQnFdeMw+iHJVxAd2CbF+VFKHyJJwnPKk=
github.com/go-critic/go-critic v0.0.0-20181204210945-ee9bf5809ead/go.mod h1:3MzXZKJdeXqdU9cj+rvZdNiN7SZ8V9OjybF8loZDmHU=
github.com/go-lintpack/lintpack v0.5.2 h1:DI5mA3+eKdWeJ40nU4d6Wc26qmdG8RCi/btYq0TuRN0=
github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
@ -96,6 +96,7 @@ github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRU
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936 h1:kw1v0NlnN+GZcU8Ma8CLF2Zzgjfx95gs3/GN3vYAPpo=

@ -1,7 +1,7 @@
MIT License
Copyright (c) 2018 Alekseev Artem
Copyright (c) 2018 Ravil Bikbulatov
Copyright (c) 2018-2019 Alekseev Artem
Copyright (c) 2018-2019 Ravil Bikbulatov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -4,9 +4,9 @@ import (
"go/ast"
"go/token"
"github.com/go-critic/go-critic/checkers/internal/lintutil"
"github.com/go-lintpack/lintpack"
"github.com/go-lintpack/lintpack/astwalk"
"github.com/go-toolsmith/astcast"
"github.com/go-toolsmith/astequal"
)
@ -73,7 +73,7 @@ func (c *appendCombineChecker) matchAppend(stmt ast.Stmt, slice ast.Expr) *ast.C
// xs are 0-N append arguments, but not variadic argument,
// because it makes append combining impossible.
assign := lintutil.AsAssignStmt(stmt)
assign := astcast.ToAssignStmt(stmt)
if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
return nil
}

@ -0,0 +1,98 @@
package checkers
import (
"go/ast"
"go/types"
"github.com/go-lintpack/lintpack"
"github.com/go-lintpack/lintpack/astwalk"
"github.com/go-toolsmith/astcast"
"github.com/go-toolsmith/astcopy"
"github.com/go-toolsmith/astp"
"github.com/go-toolsmith/typep"
)
func init() {
var info lintpack.CheckerInfo
info.Name = "argOrder"
info.Tags = []string{"diagnostic", "experimental"}
info.Summary = "Detects suspicious arguments order"
info.Before = `strings.HasPrefix("#", userpass)`
info.After = `strings.HasPrefix(userpass, "#")`
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
return astwalk.WalkerForExpr(&argOrderChecker{ctx: ctx})
})
}
type argOrderChecker struct {
astwalk.WalkHandler
ctx *lintpack.CheckerContext
}
func (c *argOrderChecker) VisitExpr(expr ast.Expr) {
call := astcast.ToCallExpr(expr)
// For now only handle functions of 2 args.
// TODO(Quasilyte): generalize the algorithm and add more patterns.
if len(call.Args) != 2 {
return
}
calledExpr := astcast.ToSelectorExpr(call.Fun)
obj, ok := c.ctx.TypesInfo.ObjectOf(astcast.ToIdent(calledExpr.X)).(*types.PkgName)
if !ok || !isStdlibPkg(obj.Imported()) {
return
}
x := call.Args[0]
y := call.Args[1]
switch calledExpr.Sel.Name {
case "HasPrefix", "HasSuffix", "Contains", "TrimPrefix", "TrimSuffix", "Split":
if obj.Name() != "bytes" && obj.Name() != "strings" {
return
}
if c.isConstLiteral(x) && !c.isConstLiteral(y) {
c.warn(call)
}
}
}
func (c *argOrderChecker) isConstLiteral(x ast.Expr) bool {
if c.ctx.TypesInfo.Types[x].Value != nil {
return true
}
// Also permit byte slices.
switch x := x.(type) {
case *ast.CallExpr:
// Handle `[]byte("abc")` as well.
if len(x.Args) != 1 || !astp.IsBasicLit(x.Args[0]) {
return false
}
typ, ok := c.ctx.TypesInfo.TypeOf(x.Fun).(*types.Slice)
return ok && typep.HasUint8Kind(typ.Elem())
case *ast.CompositeLit:
// Check if it's a const byte slice.
typ, ok := c.ctx.TypesInfo.TypeOf(x).(*types.Slice)
if !ok || !typep.HasUint8Kind(typ.Elem()) {
return false
}
for _, elt := range x.Elts {
if !astp.IsBasicLit(elt) {
return false
}
}
return true
default:
return false
}
}
func (c *argOrderChecker) warn(call *ast.CallExpr) {
fixed := astcopy.CallExpr(call)
fixed.Args[0], fixed.Args[1] = fixed.Args[1], fixed.Args[0]
c.ctx.Warn(call, "probably meant `%s`", fixed)
}

@ -1,14 +1,18 @@
package checkers
import (
"fmt"
"go/ast"
"go/token"
"strconv"
"github.com/go-critic/go-critic/checkers/internal/lintutil"
"github.com/go-lintpack/lintpack"
"github.com/go-lintpack/lintpack/astwalk"
"github.com/go-toolsmith/astcast"
"github.com/go-toolsmith/astcopy"
"github.com/go-toolsmith/astequal"
"github.com/go-toolsmith/astp"
"github.com/go-toolsmith/typep"
"golang.org/x/tools/go/ast/astutil"
)
@ -37,6 +41,10 @@ type boolExprSimplifyChecker struct {
}
func (c *boolExprSimplifyChecker) VisitExpr(x ast.Expr) {
if !astp.IsBinaryExpr(x) && !astp.IsUnaryExpr(x) {
return
}
// Throw away non-bool expressions and avoid redundant
// AST copying below.
if typ := c.ctx.TypesInfo.TypeOf(x); typ == nil || !typep.HasBoolKind(typ.Underlying()) {
@ -65,14 +73,16 @@ func (c *boolExprSimplifyChecker) simplifyBool(x ast.Expr) ast.Expr {
c.negatedEquals(cur) ||
c.invertComparison(cur) ||
c.combineChecks(cur) ||
c.removeIncDec(cur) ||
c.foldRanges(cur) ||
true
}).(ast.Expr)
}
func (c *boolExprSimplifyChecker) doubleNegation(cur *astutil.Cursor) bool {
neg1 := lintutil.AsUnaryExprOp(cur.Node(), token.NOT)
neg2 := lintutil.AsUnaryExprOp(astutil.Unparen(neg1.X), token.NOT)
if !lintutil.IsNil(neg1) && !lintutil.IsNil(neg2) {
neg1 := astcast.ToUnaryExpr(cur.Node())
neg2 := astcast.ToUnaryExpr(astutil.Unparen(neg1.X))
if neg1.Op == token.NOT && neg2.Op == token.NOT {
cur.Replace(astutil.Unparen(neg2.X))
return true
}
@ -84,9 +94,9 @@ func (c *boolExprSimplifyChecker) negatedEquals(cur *astutil.Cursor) bool {
if !ok || x.Op != token.EQL {
return false
}
neg1 := lintutil.AsUnaryExprOp(x.X, token.NOT)
neg2 := lintutil.AsUnaryExprOp(x.Y, token.NOT)
if !lintutil.IsNil(neg1) && !lintutil.IsNil(neg2) {
neg1 := astcast.ToUnaryExpr(x.X)
neg2 := astcast.ToUnaryExpr(x.Y)
if neg1.Op == token.NOT && neg2.Op == token.NOT {
x.X = neg1.X
x.Y = neg2.X
return true
@ -99,9 +109,9 @@ func (c *boolExprSimplifyChecker) invertComparison(cur *astutil.Cursor) bool {
return false
}
neg := lintutil.AsUnaryExprOp(cur.Node(), token.NOT)
cmp := lintutil.AsBinaryExpr(astutil.Unparen(neg.X))
if lintutil.IsNil(neg) || lintutil.IsNil(cmp) {
neg := astcast.ToUnaryExpr(cur.Node())
cmp := astcast.ToBinaryExpr(astutil.Unparen(neg.X))
if neg.Op != token.NOT {
return false
}
@ -127,17 +137,23 @@ func (c *boolExprSimplifyChecker) invertComparison(cur *astutil.Cursor) bool {
return true
}
func (c *boolExprSimplifyChecker) isSafe(x ast.Expr) bool {
return typep.SideEffectFree(c.ctx.TypesInfo, x)
}
func (c *boolExprSimplifyChecker) combineChecks(cur *astutil.Cursor) bool {
or := lintutil.AsBinaryExprOp(cur.Node(), token.LOR)
lhs := lintutil.AsBinaryExpr(astutil.Unparen(or.X))
rhs := lintutil.AsBinaryExpr(astutil.Unparen(or.Y))
or, ok := cur.Node().(*ast.BinaryExpr)
if !ok || or.Op != token.LOR {
return false
}
lhs := astcast.ToBinaryExpr(astutil.Unparen(or.X))
rhs := astcast.ToBinaryExpr(astutil.Unparen(or.Y))
if !astequal.Expr(lhs.X, rhs.X) || !astequal.Expr(lhs.Y, rhs.Y) {
return false
}
safe := typep.SideEffectFree(c.ctx.TypesInfo, lhs.X) &&
typep.SideEffectFree(c.ctx.TypesInfo, lhs.Y)
if !safe {
if !c.isSafe(lhs.X) || !c.isSafe(lhs.Y) {
return false
}
@ -161,6 +177,163 @@ func (c *boolExprSimplifyChecker) combineChecks(cur *astutil.Cursor) bool {
return false
}
func (c *boolExprSimplifyChecker) removeIncDec(cur *astutil.Cursor) bool {
cmp := astcast.ToBinaryExpr(cur.Node())
matchOneWay := func(op token.Token, x, y *ast.BinaryExpr) bool {
if x.Op != op || astcast.ToBasicLit(x.Y).Value != "1" {
return false
}
if y.Op == op && astcast.ToBasicLit(y.Y).Value == "1" {
return false
}
return true
}
replace := func(lhsOp, rhsOp, replacement token.Token) bool {
lhs := astcast.ToBinaryExpr(cmp.X)
rhs := astcast.ToBinaryExpr(cmp.Y)
switch {
case matchOneWay(lhsOp, lhs, rhs):
cmp.X = lhs.X
cmp.Op = replacement
cur.Replace(cmp)
return true
case matchOneWay(rhsOp, rhs, lhs):
cmp.Y = rhs.X
cmp.Op = replacement
cur.Replace(cmp)
return true
default:
return false
}
}
switch cmp.Op {
case token.GTR:
// `x > y-1` => `x >= y`
// `x+1 > y` => `x >= y`
return replace(token.ADD, token.SUB, token.GEQ)
case token.GEQ:
// `x >= y+1` => `x > y`
// `x-1 >= y` => `x > y`
return replace(token.SUB, token.ADD, token.GTR)
case token.LSS:
// `x < y+1` => `x <= y`
// `x-1 < y` => `x <= y`
return replace(token.SUB, token.ADD, token.LEQ)
case token.LEQ:
// `x <= y-1` => `x < y`
// `x+1 <= y` => `x < y`
return replace(token.ADD, token.SUB, token.LSS)
default:
return false
}
}
func (c *boolExprSimplifyChecker) foldRanges(cur *astutil.Cursor) bool {
e, ok := cur.Node().(*ast.BinaryExpr)
if !ok {
return false
}
lhs := astcast.ToBinaryExpr(e.X)
rhs := astcast.ToBinaryExpr(e.Y)
if !c.isSafe(lhs.X) || !c.isSafe(rhs.X) {
return false
}
if !astequal.Expr(lhs.X, rhs.X) {
return false
}
c1, ok := c.int64val(lhs.Y)
if !ok {
return false
}
c2, ok := c.int64val(rhs.Y)
if !ok {
return false
}
type combination struct {
lhsOp token.Token
rhsOp token.Token
rhsDiff int64
resDelta int64
}
match := func(comb *combination) bool {
if lhs.Op != comb.lhsOp || rhs.Op != comb.rhsOp {
return false
}
if c2-c1 != comb.rhsDiff {
return false
}
return true
}
switch e.Op {
case token.LAND:
combTable := [...]combination{
// `x > c && x < c+2` => `x == c+1`
{token.GTR, token.LSS, 2, 1},
// `x >= c && x < c+1` => `x == c`
{token.GEQ, token.LSS, 1, 0},
// `x > c && x <= c+1` => `x == c+1`
{token.GTR, token.LEQ, 1, 1},
// `x >= c && x <= c` => `x == c`
{token.GEQ, token.LEQ, 0, 0},
}
for _, comb := range combTable {
if match(&comb) {
lhs.Op = token.EQL
v := c1 + comb.resDelta
lhs.Y.(*ast.BasicLit).Value = fmt.Sprint(v)
cur.Replace(lhs)
return true
}
}
case token.LOR:
combTable := [...]combination{
// `x < c || x > c` => `x != c`
{token.LSS, token.GTR, 0, 0},
// `x <= c || x > c+1` => `x != c+1`
{token.LEQ, token.GTR, 1, 1},
// `x < c || x >= c+1` => `x != c`
{token.LSS, token.GEQ, 1, 0},
// `x <= c || x >= c+2` => `x != c+1`
{token.LEQ, token.GEQ, 2, 1},
}
for _, comb := range combTable {
if match(&comb) {
lhs.Op = token.NEQ
v := c1 + comb.resDelta
lhs.Y.(*ast.BasicLit).Value = fmt.Sprint(v)
cur.Replace(lhs)
return true
}
}
}
return false
}
func (c *boolExprSimplifyChecker) int64val(x ast.Expr) (int64, bool) {
// TODO(Quasilyte): if we had types info, we could use TypesInfo.Types[x].Value,
// but since copying erases leaves us without it, only basic literals are handled.
lit, ok := x.(*ast.BasicLit)
if !ok {
return 0, false
}
v, err := strconv.ParseInt(lit.Value, 10, 64)
if err != nil {
return 0, false
}
return v, true
}
func (c *boolExprSimplifyChecker) warn(cause, suggestion ast.Expr) {
c.SkipChilds = true
c.ctx.Warn(cause, "can simplify `%s` to `%s`", cause, suggestion)

@ -16,68 +16,17 @@ func init() {
info.After = `length := 10`
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
builtins := map[string]bool{
// Types
"bool": true,
"byte": true,
"complex64": true,
"complex128": true,
"error": true,
"float32": true,
"float64": true,
"int": true,
"int8": true,
"int16": true,
"int32": true,
"int64": true,
"rune": true,
"string": true,
"uint": true,
"uint8": true,
"uint16": true,
"uint32": true,
"uint64": true,
"uintptr": true,
// Constants
"true": true,
"false": true,
"iota": true,
// Zero value
"nil": true,
// Functions
"append": true,
"cap": true,
"close": true,
"complex": true,
"copy": true,
"delete": true,
"imag": true,
"len": true,
"make": true,
"new": true,
"panic": true,
"print": true,
"println": true,
"real": true,
"recover": true,
}
c := &builtinShadowChecker{ctx: ctx, builtins: builtins}
return astwalk.WalkerForLocalDef(c, ctx.TypesInfo)
return astwalk.WalkerForLocalDef(&builtinShadowChecker{ctx: ctx}, ctx.TypesInfo)
})
}
type builtinShadowChecker struct {
astwalk.WalkHandler
ctx *lintpack.CheckerContext
builtins map[string]bool
}
func (c *builtinShadowChecker) VisitLocalDef(name astwalk.Name, _ ast.Expr) {
if _, isBuiltin := c.builtins[name.ID.String()]; isBuiltin {
if isBuiltin(name.ID.Name) {
c.warn(name.ID)
}
}

@ -0,0 +1,61 @@
package checkers
import (
"go/ast"
"regexp"
"strings"
"github.com/go-lintpack/lintpack"
"github.com/go-lintpack/lintpack/astwalk"
)
func init() {
var info lintpack.CheckerInfo
info.Name = "codegenComment"
info.Tags = []string{"diagnostic", "experimental"}
info.Summary = "Detects malformed 'code generated' file comments"
info.Before = `// This file was automatically generated by foogen`
info.After = `// Code generated by foogen. DO NOT EDIT.`
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
patterns := []string{
"this (?:file|code) (?:was|is) auto(?:matically)? generated",
"this (?:file|code) (?:was|is) generated automatically",
"this (?:file|code) (?:was|is) generated by",
"this (?:file|code) (?:was|is) (?:auto(?:matically)? )?generated",
"this (?:file|code) (?:was|is) generated",
"code in this file (?:was|is) auto(?:matically)? generated",
"generated (?:file|code) - do not edit",
// TODO(Quasilyte): more of these.
}
re := regexp.MustCompile("(?i)" + strings.Join(patterns, "|"))
return &codegenCommentChecker{
ctx: ctx,
badCommentRE: re,
}
})
}
type codegenCommentChecker struct {
astwalk.WalkHandler
ctx *lintpack.CheckerContext
badCommentRE *regexp.Regexp
}
func (c *codegenCommentChecker) WalkFile(f *ast.File) {
if f.Doc == nil {
return
}
for _, comment := range f.Doc.List {
if c.badCommentRE.MatchString(comment.Text) {
c.warn(comment)
return
}
}
}
func (c *codegenCommentChecker) warn(cause ast.Node) {
c.ctx.Warn(cause, "comment should match `Code generated .* DO NOT EDIT.` regexp")
}

@ -20,7 +20,13 @@ func init() {
info.After = `// This is a comment`
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
pragmaRE := regexp.MustCompile(`(?m)^//\w+:.*$`)
parts := []string{
`^//\w+:.*$`, //key: value
`^//nolint$`, //nolint
`^//line /.*:\d+`, //line /path/to/file:123
}
pat := "(?m)" + strings.Join(parts, "|")
pragmaRE := regexp.MustCompile(pat)
return astwalk.WalkerForComment(&commentFormattingChecker{
ctx: ctx,
pragmaRE: pragmaRE,

@ -27,6 +27,8 @@ func FuncOld() int`
c.commonPatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)this (?:function|type) is deprecated`),
regexp.MustCompile(`(?i)deprecated[.!]? use \S* instead`),
regexp.MustCompile(`(?i)\[\[deprecated\]\].*`),
regexp.MustCompile(`(?i)note: deprecated\b.*`),
// TODO(quasilyte): more of these?
}
@ -46,6 +48,7 @@ func FuncOld() int`
"Deprecate: ",
"Derpecate: ",
"Derpecated: ",
"Depreacted: ",
}
for i := range c.commonTypos {
c.commonTypos[i] = strings.ToUpper(c.commonTypos[i])

@ -48,6 +48,9 @@ func init() {
c.matchers = map[string]func(*ast.CallExpr) bool{
"copy": m["(x, x, ...)"],
"math.Max": m["(x, x, ...)"],
"math.Min": m["(x, x, ...)"],
"reflect.Copy": m["(x, x, ...)"],
"reflect.DeepEqual": m["(x, x, ...)"],

@ -7,6 +7,7 @@ import (
"github.com/go-lintpack/lintpack"
"github.com/go-lintpack/lintpack/astwalk"
"github.com/go-toolsmith/astcast"
"github.com/go-toolsmith/astequal"
)
func init() {
@ -57,7 +58,9 @@ func (c *equalFoldChecker) checkBytes(expr *ast.CallExpr) {
if !ok1 && !ok2 {
return
}
c.warnBytes(expr, x, y)
if !astequal.Expr(x, y) {
c.warnBytes(expr, x, y)
}
}
func (c *equalFoldChecker) checkStrings(expr *ast.BinaryExpr) {
@ -70,7 +73,9 @@ func (c *equalFoldChecker) checkStrings(expr *ast.BinaryExpr) {
if !ok1 && !ok2 {
return
}
c.warnStrings(expr, x, y)
if !astequal.Expr(x, y) {
c.warnStrings(expr, x, y)
}
}
func (c *equalFoldChecker) warnStrings(cause ast.Node, x, y ast.Expr) {

@ -3,6 +3,7 @@ package checkers
import (
"go/ast"
"go/constant"
"go/types"
"strings"
"github.com/go-lintpack/lintpack"
@ -30,13 +31,14 @@ type flagNameChecker struct {
func (c *flagNameChecker) VisitExpr(expr ast.Expr) {
call := astcast.ToCallExpr(expr)
sym := astcast.ToIdent(astcast.ToSelectorExpr(call.Fun).Sel)
obj := c.ctx.TypesInfo.ObjectOf(sym)
if obj == nil {
calledExpr := astcast.ToSelectorExpr(call.Fun)
obj, ok := c.ctx.TypesInfo.ObjectOf(astcast.ToIdent(calledExpr.X)).(*types.PkgName)
if !ok {
return
}
pkg := obj.Pkg()
if !isStdlibPkg(pkg) || pkg.Name() != "flag" {
sym := calledExpr.Sel
pkg := obj.Imported()
if pkg.Path() != "flag" {
return
}

@ -0,0 +1,60 @@
package checkers
import (
"go/ast"
"go/token"
"strings"
"github.com/go-lintpack/lintpack"
"github.com/go-lintpack/lintpack/astwalk"
"github.com/go-toolsmith/astcast"
)
func init() {
var info lintpack.CheckerInfo
info.Name = "hexLiteral"
info.Tags = []string{"style", "experimental"}
info.Summary = "Detects hex literals that have mixed case letter digits"
info.Before = `
x := 0X12
y := 0xfF`
info.After = `
x := 0x12
// (A)
y := 0xff
// (B)
y := 0xFF`
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
return astwalk.WalkerForExpr(&hexLiteralChecker{ctx: ctx})
})
}
type hexLiteralChecker struct {
astwalk.WalkHandler
ctx *lintpack.CheckerContext
}
func (c *hexLiteralChecker) warn0X(lit *ast.BasicLit) {
suggest := "0x" + lit.Value[len("0X"):]
c.ctx.Warn(lit, "prefer 0x over 0X, s/%s/%s/", lit.Value, suggest)
}
func (c *hexLiteralChecker) warnMixedDigits(lit *ast.BasicLit) {
c.ctx.Warn(lit, "don't mix hex literal letter digits casing")
}
func (c *hexLiteralChecker) VisitExpr(expr ast.Expr) {
lit := astcast.ToBasicLit(expr)
if lit.Kind != token.INT || len(lit.Value) < 3 {
return
}
if strings.HasPrefix(lit.Value, "0X") {
c.warn0X(lit)
return
}
digits := lit.Value[len("0x"):]
if strings.ToLower(digits) != digits && strings.ToUpper(digits) != digits {
c.warnMixedDigits(lit)
}
}

@ -1,121 +0,0 @@
package lintutil
import (
"go/ast"
"go/token"
)
var (
nilIdent = &ast.Ident{}
nilSelectorExpr = &ast.SelectorExpr{}
nilUnaryExpr = &ast.UnaryExpr{}
nilBinaryExpr = &ast.BinaryExpr{}
nilCallExpr = &ast.CallExpr{}
nilParenExpr = &ast.ParenExpr{}
nilAssignStmt = &ast.AssignStmt{}
)
// IsNil reports whether x is nil.
// Unlike simple nil check, also detects nil AST sentinels.
func IsNil(x ast.Node) bool {
switch x := x.(type) {
case *ast.Ident:
return x == nilIdent || x == nil
case *ast.SelectorExpr:
return x == nilSelectorExpr || x == nil
case *ast.UnaryExpr:
return x == nilUnaryExpr || x == nil
case *ast.BinaryExpr:
return x == nilBinaryExpr || x == nil
case *ast.CallExpr:
return x == nilCallExpr || x == nil
case *ast.ParenExpr:
return x == nilParenExpr || x == nil
case *ast.AssignStmt:
return x == nilAssignStmt || x == nil
default:
return x == nil
}
}
// AsIdent coerces x into non-nil ident.
func AsIdent(x ast.Node) *ast.Ident {
e, ok := x.(*ast.Ident)
if !ok {
return nilIdent
}
return e
}
// AsSelectorExpr coerces x into non-nil selector expr.
func AsSelectorExpr(x ast.Node) *ast.SelectorExpr {
e, ok := x.(*ast.SelectorExpr)
if !ok {
return nilSelectorExpr
}
return e
}
// AsUnaryExpr coerces x into non-nil unary expr.
func AsUnaryExpr(x ast.Node) *ast.UnaryExpr {
e, ok := x.(*ast.UnaryExpr)
if !ok {
return nilUnaryExpr
}
return e
}
// AsUnaryExprOp is like AsUnaryExpr, but also checks for op token.
func AsUnaryExprOp(x ast.Node, op token.Token) *ast.UnaryExpr {
e, ok := x.(*ast.UnaryExpr)
if !ok || e.Op != op {
return nilUnaryExpr
}
return e
}
// AsBinaryExpr coerces x into non-nil binary expr.
func AsBinaryExpr(x ast.Node) *ast.BinaryExpr {
e, ok := x.(*ast.BinaryExpr)
if !ok {
return nilBinaryExpr
}
return e
}
// AsBinaryExprOp is like AsBinaryExpr, but also checks for op token.
func AsBinaryExprOp(x ast.Node, op token.Token) *ast.BinaryExpr {
e, ok := x.(*ast.BinaryExpr)
if !ok || e.Op != op {
return nilBinaryExpr
}
return e
}
// AsCallExpr coerces x into non-nil call expr.
func AsCallExpr(x ast.Node) *ast.CallExpr {
e, ok := x.(*ast.CallExpr)
if !ok {
return nilCallExpr
}
return e
}
// AsParenExpr coerces x into non-nil paren expr.
func AsParenExpr(x ast.Node) *ast.ParenExpr {
e, ok := x.(*ast.ParenExpr)
if !ok {
return nilParenExpr
}
return e
}
// AsAssignStmt coerces x into non-nil assign stmt.
func AsAssignStmt(x ast.Node) *ast.AssignStmt {
stmt, ok := x.(*ast.AssignStmt)
if !ok {
return nilAssignStmt
}
return stmt
}

@ -0,0 +1,82 @@
package checkers
import (
"go/ast"
"go/token"
"go/types"
"github.com/go-lintpack/lintpack"
"github.com/go-lintpack/lintpack/astwalk"
"github.com/go-toolsmith/astcast"
)
func init() {
var info lintpack.CheckerInfo
info.Name = "octalLiteral"
info.Tags = []string{"diagnostic", "experimental"}
info.Summary = "Detects octal literals passed to functions"
info.Before = `foo(02)`
info.After = `foo(2)`
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
c := &octalLiteralChecker{
ctx: ctx,
octFriendlyPkg: map[string]bool{
"os": true,
"io/ioutil": true,
},
}
return astwalk.WalkerForExpr(c)
})
}
type octalLiteralChecker struct {
astwalk.WalkHandler
ctx *lintpack.CheckerContext
octFriendlyPkg map[string]bool
}
func (c *octalLiteralChecker) VisitExpr(expr ast.Expr) {
call := astcast.ToCallExpr(expr)
calledExpr := astcast.ToSelectorExpr(call.Fun)
ident := astcast.ToIdent(calledExpr.X)
if obj, ok := c.ctx.TypesInfo.ObjectOf(ident).(*types.PkgName); ok {
pkg := obj.Imported()
if c.octFriendlyPkg[pkg.Path()] {
return
}
}
for _, arg := range call.Args {
if lit := astcast.ToBasicLit(c.unsign(arg)); len(lit.Value) > 1 &&
c.isIntLiteral(lit) &&
c.isOctalLiteral(lit) {
c.warn(call)
return
}
}
}
func (c *octalLiteralChecker) unsign(e ast.Expr) ast.Expr {
u, ok := e.(*ast.UnaryExpr)
if !ok {
return e
}
return u.X
}
func (c *octalLiteralChecker) isIntLiteral(lit *ast.BasicLit) bool {
return lit.Kind == token.INT
}
func (c *octalLiteralChecker) isOctalLiteral(lit *ast.BasicLit) bool {
return lit.Value[0] == '0' &&
lit.Value[1] != 'x' &&
lit.Value[1] != 'X'
}
func (c *octalLiteralChecker) warn(expr ast.Expr) {
c.ctx.Warn(expr, "suspicious octal args in `%s`", expr)
}

@ -0,0 +1,47 @@
package checkers
import (
"go/ast"
"github.com/go-lintpack/lintpack"
"github.com/go-lintpack/lintpack/astwalk"
"github.com/go-toolsmith/typep"
)
func init() {
var info lintpack.CheckerInfo
info.Name = "stringXbytes"
info.Tags = []string{"style", "experimental"}
info.Summary = "Detects redundant conversions between string and []byte"
info.Before = `copy(b, []byte(s))`
info.After = `copy(b, s)`
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
return astwalk.WalkerForExpr(&stringXbytes{ctx: ctx})
})
}
type stringXbytes struct {
astwalk.WalkHandler
ctx *lintpack.CheckerContext
}
func (c *stringXbytes) VisitExpr(expr ast.Expr) {
x, ok := expr.(*ast.CallExpr)
if !ok || qualifiedName(x.Fun) != "copy" {
return
}
src := x.Args[1]
byteCast, ok := src.(*ast.CallExpr)
if ok && typep.IsTypeExpr(c.ctx.TypesInfo, byteCast.Fun) &&
typep.HasStringProp(c.ctx.TypesInfo.TypeOf(byteCast.Args[0])) {
c.warn(byteCast, byteCast.Args[0])
}
}
func (c *stringXbytes) warn(cause *ast.CallExpr, suggestion ast.Expr) {
c.ctx.Warn(cause, "can simplify `%s` to `%s`", cause, suggestion)
}

@ -0,0 +1,132 @@
package checkers
import (
"go/ast"
"go/token"
"github.com/go-critic/go-critic/checkers/internal/lintutil"
"github.com/go-lintpack/lintpack"
"github.com/go-lintpack/lintpack/astwalk"
"github.com/go-toolsmith/astcast"
"github.com/go-toolsmith/astequal"
"github.com/go-toolsmith/astp"
)
func init() {
var info lintpack.CheckerInfo
info.Name = "typeAssertChain"
info.Tags = []string{"style", "experimental"}
info.Summary = "Detects repeated type assertions and suggests to replace them with type switch statement"
info.Before = `
if x, ok := v.(T1); ok {
// Code A, uses x.
} else if x, ok := v.(T2); ok {
// Code B, uses x.
} else if x, ok := v.(T3); ok {
// Code C, uses x.
}`
info.After = `
switch x := v.(T1) {
case cond1:
// Code A, uses x.
case cond2:
// Code B, uses x.
default:
// Code C, uses x.
}`
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
return astwalk.WalkerForStmt(&typeAssertChainChecker{ctx: ctx})
})
}
type typeAssertChainChecker struct {
astwalk.WalkHandler
ctx *lintpack.CheckerContext
cause *ast.IfStmt
visited map[*ast.IfStmt]bool
typeSet lintutil.AstSet
}
func (c *typeAssertChainChecker) EnterFunc(fn *ast.FuncDecl) bool {
if fn.Body == nil {
return false
}
c.visited = make(map[*ast.IfStmt]bool)
return true
}
func (c *typeAssertChainChecker) VisitStmt(stmt ast.Stmt) {
ifstmt, ok := stmt.(*ast.IfStmt)
if !ok || c.visited[ifstmt] || ifstmt.Init == nil {
return
}
assertion := c.getTypeAssert(ifstmt)
if assertion == nil {
return
}
c.cause = ifstmt
c.checkIfStmt(ifstmt, assertion)
}
func (c *typeAssertChainChecker) getTypeAssert(ifstmt *ast.IfStmt) *ast.TypeAssertExpr {
assign := astcast.ToAssignStmt(ifstmt.Init)
if len(assign.Lhs) != 2 || len(assign.Rhs) != 1 {
return nil
}
if !astp.IsIdent(assign.Lhs[0]) || assign.Tok != token.DEFINE {
return nil
}
if !astequal.Expr(assign.Lhs[1], ifstmt.Cond) {
return nil
}
assertion, ok := assign.Rhs[0].(*ast.TypeAssertExpr)
if !ok {
return nil
}
return assertion
}
func (c *typeAssertChainChecker) checkIfStmt(stmt *ast.IfStmt, assertion *ast.TypeAssertExpr) {
if c.countTypeAssertions(stmt, assertion) >= 2 {
c.warn()
}
}
func (c *typeAssertChainChecker) countTypeAssertions(stmt *ast.IfStmt, assertion *ast.TypeAssertExpr) int {
c.typeSet.Clear()
count := 1
x := assertion.X
c.typeSet.Insert(assertion.Type)
for {
e, ok := stmt.Else.(*ast.IfStmt)
if !ok {
return count
}
assertion = c.getTypeAssert(e)
if assertion == nil {
return count
}
if !c.typeSet.Insert(assertion.Type) {
// Asserted type is duplicated.
// Type switch does not permit duplicate cases,
// so give up.
return 0
}
if !astequal.Expr(x, assertion.X) {
// Mixed type asserting chain.
// Can't be easily translated to a type switch.
return 0
}
stmt = e
count++
c.visited[e] = true
}
}
func (c *typeAssertChainChecker) warn() {
c.ctx.Warn(c.cause, "rewrite if-else to type switch statement")
}

@ -4,9 +4,9 @@ import (
"go/ast"
"go/types"
"github.com/go-critic/go-critic/checkers/internal/lintutil"
"github.com/go-lintpack/lintpack"
"github.com/go-lintpack/lintpack/astwalk"
"github.com/go-toolsmith/astcast"
"github.com/go-toolsmith/astp"
)
@ -45,7 +45,7 @@ type underefChecker struct {
func (c *underefChecker) VisitExpr(expr ast.Expr) {
switch n := expr.(type) {
case *ast.SelectorExpr:
expr := lintutil.AsParenExpr(n.X)
expr := astcast.ToParenExpr(n.X)
if c.skipRecvDeref && c.isPtrRecvMethodCall(n.Sel) {
return
}
@ -56,7 +56,7 @@ func (c *underefChecker) VisitExpr(expr ast.Expr) {
}
}
case *ast.IndexExpr:
expr := lintutil.AsParenExpr(n.X)
expr := astcast.ToParenExpr(n.X)
if expr, ok := expr.X.(*ast.StarExpr); ok {
if !c.checkStarExpr(expr) {
return

@ -4,9 +4,9 @@ import (
"go/ast"
"go/types"
"github.com/go-critic/go-critic/checkers/internal/lintutil"
"github.com/go-lintpack/lintpack"
"github.com/go-lintpack/lintpack/astwalk"
"github.com/go-toolsmith/astcast"
"github.com/go-toolsmith/astequal"
)
@ -39,11 +39,14 @@ func (c *unlambdaChecker) VisitExpr(x ast.Expr) {
return
}
result := lintutil.AsCallExpr(ret.Results[0])
result := astcast.ToCallExpr(ret.Results[0])
callable := qualifiedName(result.Fun)
if callable == "" {
return // Skip tricky cases; only handle simple calls
}
if isBuiltin(callable) {
return // See #762
}
fnType := c.ctx.TypesInfo.TypeOf(fn)
resultType := c.ctx.TypesInfo.TypeOf(result.Fun)
if !types.Identical(fnType, resultType) {

@ -44,7 +44,7 @@ func (c *unnamedResultChecker) VisitFuncDecl(decl *ast.FuncDecl) {
switch {
case results == nil:
return // Function has no results
case len(results.List) > 0 && results.List[0].Names != nil:
case len(results.List) != 0 && results.List[0].Names != nil:
return // Skip named results
}

@ -8,6 +8,60 @@ import (
"github.com/go-lintpack/lintpack"
)
var goBuiltins = map[string]bool{
// Types
"bool": true,
"byte": true,
"complex64": true,
"complex128": true,
"error": true,
"float32": true,
"float64": true,
"int": true,
"int8": true,
"int16": true,
"int32": true,
"int64": true,
"rune": true,
"string": true,
"uint": true,
"uint8": true,
"uint16": true,
"uint32": true,
"uint64": true,
"uintptr": true,
// Constants
"true": true,
"false": true,
"iota": true,
// Zero value
"nil": true,
// Functions
"append": true,
"cap": true,
"close": true,
"complex": true,
"copy": true,
"delete": true,
"imag": true,
"len": true,
"make": true,
"new": true,
"panic": true,
"print": true,
"println": true,
"real": true,
"recover": true,
}
// isBuiltin reports whether sym belongs to a predefined identifier set.
func isBuiltin(sym string) bool {
return goBuiltins[sym]
}
// isStdlibPkg reports whether pkg is a package from the Go standard library.
func isStdlibPkg(pkg *types.Package) bool {
return pkg != nil && pkg.Path() == pkg.Name()

@ -0,0 +1,723 @@
// Copyright 2019 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.
// Indexed binary package export.
// This file was derived from $GOROOT/src/cmd/compile/internal/gc/iexport.go;
// see that file for specification of the format.
// +build go1.11
package gcimporter
import (
"bytes"
"encoding/binary"
"go/ast"
"go/constant"
"go/token"
"go/types"
"io"
"math/big"
"reflect"
"sort"
)
// Current indexed export format version. Increase with each format change.
// 0: Go1.11 encoding
const iexportVersion = 0
// IExportData returns the binary export data for pkg.
// If no file set is provided, position info will be missing.
func IExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) {
defer func() {
if e := recover(); e != nil {
if ierr, ok := e.(internalError); ok {
err = ierr
return
}
// Not an internal error; panic again.
panic(e)
}
}()
p := iexporter{
out: bytes.NewBuffer(nil),
fset: fset,
allPkgs: map[*types.Package]bool{},
stringIndex: map[string]uint64{},
declIndex: map[types.Object]uint64{},
typIndex: map[types.Type]uint64{},
}
for i, pt := range predeclared() {
p.typIndex[pt] = uint64(i)
}
if len(p.typIndex) > predeclReserved {
panic(internalErrorf("too many predeclared types: %d > %d", len(p.typIndex), predeclReserved))
}
// Initialize work queue with exported declarations.
scope := pkg.Scope()
for _, name := range scope.Names() {
if ast.IsExported(name) {
p.pushDecl(scope.Lookup(name))
}
}
// Loop until no more work.
for !p.declTodo.empty() {
p.doDecl(p.declTodo.popHead())
}
// Append indices to data0 section.
dataLen := uint64(p.data0.Len())
w := p.newWriter()
w.writeIndex(p.declIndex, pkg)
w.flush()
// Assemble header.
var hdr intWriter
hdr.WriteByte('i')
hdr.uint64(iexportVersion)
hdr.uint64(uint64(p.strings.Len()))
hdr.uint64(dataLen)
// Flush output.
io.Copy(p.out, &hdr)
io.Copy(p.out, &p.strings)
io.Copy(p.out, &p.data0)
return p.out.Bytes(), nil
}
// writeIndex writes out an object index. mainIndex indicates whether
// we're writing out the main index, which is also read by
// non-compiler tools and includes a complete package description
// (i.e., name and height).
func (w *exportWriter) writeIndex(index map[types.Object]uint64, localpkg *types.Package) {
// Build a map from packages to objects from that package.
pkgObjs := map[*types.Package][]types.Object{}
// For the main index, make sure to include every package that
// we reference, even if we're not exporting (or reexporting)
// any symbols from it.
pkgObjs[localpkg] = nil
for pkg := range w.p.allPkgs {
pkgObjs[pkg] = nil
}
for obj := range index {
pkgObjs[obj.Pkg()] = append(pkgObjs[obj.Pkg()], obj)
}
var pkgs []*types.Package
for pkg, objs := range pkgObjs {
pkgs = append(pkgs, pkg)
sort.Slice(objs, func(i, j int) bool {
return objs[i].Name() < objs[j].Name()
})
}
sort.Slice(pkgs, func(i, j int) bool {
return pkgs[i].Path() < pkgs[j].Path()
})
w.uint64(uint64(len(pkgs)))
for _, pkg := range pkgs {
w.string(pkg.Path())
w.string(pkg.Name())
w.uint64(uint64(0)) // package height is not needed for go/types
objs := pkgObjs[pkg]
w.uint64(uint64(len(objs)))
for _, obj := range objs {
w.string(obj.Name())
w.uint64(index[obj])
}
}
}
type iexporter struct {
fset *token.FileSet
out *bytes.Buffer
// allPkgs tracks all packages that have been referenced by
// the export data, so we can ensure to include them in the
// main index.
allPkgs map[*types.Package]bool
declTodo objQueue
strings intWriter
stringIndex map[string]uint64
data0 intWriter
declIndex map[types.Object]uint64
typIndex map[types.Type]uint64
}
// stringOff returns the offset of s within the string section.
// If not already present, it's added to the end.
func (p *iexporter) stringOff(s string) uint64 {
off, ok := p.stringIndex[s]
if !ok {
off = uint64(p.strings.Len())
p.stringIndex[s] = off
p.strings.uint64(uint64(len(s)))
p.strings.WriteString(s)
}
return off
}
// pushDecl adds n to the declaration work queue, if not already present.
func (p *iexporter) pushDecl(obj types.Object) {
// Package unsafe is known to the compiler and predeclared.
assert(obj.Pkg() != types.Unsafe)
if _, ok := p.declIndex[obj]; ok {
return
}
p.declIndex[obj] = ^uint64(0) // mark n present in work queue
p.declTodo.pushTail(obj)
}
// exportWriter handles writing out individual data section chunks.
type exportWriter struct {
p *iexporter
data intWriter
currPkg *types.Package
prevFile string
prevLine int64
}
func (p *iexporter) doDecl(obj types.Object) {
w := p.newWriter()
w.setPkg(obj.Pkg(), false)
switch obj := obj.(type) {
case *types.Var:
w.tag('V')
w.pos(obj.Pos())
w.typ(obj.Type(), obj.Pkg())
case *types.Func:
sig, _ := obj.Type().(*types.Signature)
if sig.Recv() != nil {
panic(internalErrorf("unexpected method: %v", sig))
}
w.tag('F')
w.pos(obj.Pos())
w.signature(sig)
case *types.Const:
w.tag('C')
w.pos(obj.Pos())
w.value(obj.Type(), obj.Val())
case *types.TypeName:
if obj.IsAlias() {
w.tag('A')
w.pos(obj.Pos())
w.typ(obj.Type(), obj.Pkg())
break
}
// Defined type.
w.tag('T')
w.pos(obj.Pos())
underlying := obj.Type().Underlying()
w.typ(underlying, obj.Pkg())
t := obj.Type()
if types.IsInterface(t) {
break
}
named, ok := t.(*types.Named)
if !ok {
panic(internalErrorf("%s is not a defined type", t))
}
n := named.NumMethods()
w.uint64(uint64(n))
for i := 0; i < n; i++ {
m := named.Method(i)
w.pos(m.Pos())
w.string(m.Name())
sig, _ := m.Type().(*types.Signature)
w.param(sig.Recv())
w.signature(sig)
}
default:
panic(internalErrorf("unexpected object: %v", obj))
}
p.declIndex[obj] = w.flush()
}
func (w *exportWriter) tag(tag byte) {
w.data.WriteByte(tag)
}
func (w *exportWriter) pos(pos token.Pos) {
p := w.p.fset.Position(pos)
file := p.Filename
line := int64(p.Line)
// When file is the same as the last position (common case),
// we can save a few bytes by delta encoding just the line
// number.
//
// Note: Because data objects may be read out of order (or not
// at all), we can only apply delta encoding within a single
// object. This is handled implicitly by tracking prevFile and
// prevLine as fields of exportWriter.
if file == w.prevFile {
delta := line - w.prevLine
w.int64(delta)
if delta == deltaNewFile {
w.int64(-1)
}
} else {
w.int64(deltaNewFile)
w.int64(line) // line >= 0
w.string(file)
w.prevFile = file
}
w.prevLine = line
}
func (w *exportWriter) pkg(pkg *types.Package) {
// Ensure any referenced packages are declared in the main index.
w.p.allPkgs[pkg] = true
w.string(pkg.Path())
}
func (w *exportWriter) qualifiedIdent(obj types.Object) {
// Ensure any referenced declarations are written out too.
w.p.pushDecl(obj)
w.string(obj.Name())
w.pkg(obj.Pkg())
}
func (w *exportWriter) typ(t types.Type, pkg *types.Package) {
w.data.uint64(w.p.typOff(t, pkg))
}
func (p *iexporter) newWriter() *exportWriter {
return &exportWriter{p: p}
}
func (w *exportWriter) flush() uint64 {
off := uint64(w.p.data0.Len())
io.Copy(&w.p.data0, &w.data)
return off
}
func (p *iexporter) typOff(t types.Type, pkg *types.Package) uint64 {
off, ok := p.typIndex[t]
if !ok {
w := p.newWriter()
w.doTyp(t, pkg)
off = predeclReserved + w.flush()
p.typIndex[t] = off
}
return off
}
func (w *exportWriter) startType(k itag) {
w.data.uint64(uint64(k))
}
func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) {
switch t := t.(type) {
case *types.Named:
w.startType(definedType)
w.qualifiedIdent(t.Obj())
case *types.Pointer:
w.startType(pointerType)
w.typ(t.Elem(), pkg)
case *types.Slice:
w.startType(sliceType)
w.typ(t.Elem(), pkg)
case *types.Array:
w.startType(arrayType)
w.uint64(uint64(t.Len()))
w.typ(t.Elem(), pkg)
case *types.Chan:
w.startType(chanType)
// 1 RecvOnly; 2 SendOnly; 3 SendRecv
var dir uint64
switch t.Dir() {
case types.RecvOnly:
dir = 1
case types.SendOnly:
dir = 2
case types.SendRecv:
dir = 3
}
w.uint64(dir)
w.typ(t.Elem(), pkg)
case *types.Map:
w.startType(mapType)
w.typ(t.Key(), pkg)
w.typ(t.Elem(), pkg)
case *types.Signature:
w.startType(signatureType)
w.setPkg(pkg, true)
w.signature(t)
case *types.Struct:
w.startType(structType)
w.setPkg(pkg, true)
n := t.NumFields()
w.uint64(uint64(n))
for i := 0; i < n; i++ {
f := t.Field(i)
w.pos(f.Pos())
w.string(f.Name())
w.typ(f.Type(), pkg)
w.bool(f.Embedded())
w.string(t.Tag(i)) // note (or tag)
}
case *types.Interface:
w.startType(interfaceType)
w.setPkg(pkg, true)
n := t.NumEmbeddeds()
w.uint64(uint64(n))
for i := 0; i < n; i++ {
f := t.Embedded(i)
w.pos(f.Obj().Pos())
w.typ(f.Obj().Type(), f.Obj().Pkg())
}
n = t.NumExplicitMethods()
w.uint64(uint64(n))
for i := 0; i < n; i++ {
m := t.ExplicitMethod(i)
w.pos(m.Pos())
w.string(m.Name())
sig, _ := m.Type().(*types.Signature)
w.signature(sig)
}
default:
panic(internalErrorf("unexpected type: %v, %v", t, reflect.TypeOf(t)))
}
}
func (w *exportWriter) setPkg(pkg *types.Package, write bool) {
if write {
w.pkg(pkg)
}
w.currPkg = pkg
}
func (w *exportWriter) signature(sig *types.Signature) {
w.paramList(sig.Params())
w.paramList(sig.Results())
if sig.Params().Len() > 0 {
w.bool(sig.Variadic())
}
}
func (w *exportWriter) paramList(tup *types.Tuple) {
n := tup.Len()
w.uint64(uint64(n))
for i := 0; i < n; i++ {
w.param(tup.At(i))
}
}
func (w *exportWriter) param(obj types.Object) {
w.pos(obj.Pos())
w.localIdent(obj)
w.typ(obj.Type(), obj.Pkg())
}
func (w *exportWriter) value(typ types.Type, v constant.Value) {
w.typ(typ, nil)
switch v.Kind() {
case constant.Bool:
w.bool(constant.BoolVal(v))
case constant.Int:
var i big.Int
if i64, exact := constant.Int64Val(v); exact {
i.SetInt64(i64)
} else if ui64, exact := constant.Uint64Val(v); exact {
i.SetUint64(ui64)
} else {
i.SetString(v.ExactString(), 10)
}
w.mpint(&i, typ)
case constant.Float:
f := constantToFloat(v)
w.mpfloat(f, typ)
case constant.Complex:
w.mpfloat(constantToFloat(constant.Real(v)), typ)
w.mpfloat(constantToFloat(constant.Imag(v)), typ)
case constant.String:
w.string(constant.StringVal(v))
case constant.Unknown:
// package contains type errors
default:
panic(internalErrorf("unexpected value %v (%T)", v, v))
}
}
// constantToFloat converts a constant.Value with kind constant.Float to a
// big.Float.
func constantToFloat(x constant.Value) *big.Float {
assert(x.Kind() == constant.Float)
// Use the same floating-point precision (512) as cmd/compile
// (see Mpprec in cmd/compile/internal/gc/mpfloat.go).
const mpprec = 512
var f big.Float
f.SetPrec(mpprec)
if v, exact := constant.Float64Val(x); exact {
// float64
f.SetFloat64(v)
} else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int {
// TODO(gri): add big.Rat accessor to constant.Value.
n := valueToRat(num)
d := valueToRat(denom)
f.SetRat(n.Quo(n, d))
} else {
// Value too large to represent as a fraction => inaccessible.
// TODO(gri): add big.Float accessor to constant.Value.
_, ok := f.SetString(x.ExactString())
assert(ok)
}
return &f
}
// mpint exports a multi-precision integer.
//
// For unsigned types, small values are written out as a single
// byte. Larger values are written out as a length-prefixed big-endian
// byte string, where the length prefix is encoded as its complement.
// For example, bytes 0, 1, and 2 directly represent the integer
// values 0, 1, and 2; while bytes 255, 254, and 253 indicate a 1-,
// 2-, and 3-byte big-endian string follow.
//
// Encoding for signed types use the same general approach as for
// unsigned types, except small values use zig-zag encoding and the
// bottom bit of length prefix byte for large values is reserved as a
// sign bit.
//
// The exact boundary between small and large encodings varies
// according to the maximum number of bytes needed to encode a value
// of type typ. As a special case, 8-bit types are always encoded as a
// single byte.
//
// TODO(mdempsky): Is this level of complexity really worthwhile?
func (w *exportWriter) mpint(x *big.Int, typ types.Type) {
basic, ok := typ.Underlying().(*types.Basic)
if !ok {
panic(internalErrorf("unexpected type %v (%T)", typ.Underlying(), typ.Underlying()))
}
signed, maxBytes := intSize(basic)
negative := x.Sign() < 0
if !signed && negative {
panic(internalErrorf("negative unsigned integer; type %v, value %v", typ, x))
}
b := x.Bytes()
if len(b) > 0 && b[0] == 0 {
panic(internalErrorf("leading zeros"))
}
if uint(len(b)) > maxBytes {
panic(internalErrorf("bad mpint length: %d > %d (type %v, value %v)", len(b), maxBytes, typ, x))
}
maxSmall := 256 - maxBytes
if signed {
maxSmall = 256 - 2*maxBytes
}
if maxBytes == 1 {
maxSmall = 256
}
// Check if x can use small value encoding.
if len(b) <= 1 {
var ux uint
if len(b) == 1 {
ux = uint(b[0])
}
if signed {
ux <<= 1
if negative {
ux--
}
}
if ux < maxSmall {
w.data.WriteByte(byte(ux))
return
}
}
n := 256 - uint(len(b))
if signed {
n = 256 - 2*uint(len(b))
if negative {
n |= 1
}
}
if n < maxSmall || n >= 256 {
panic(internalErrorf("encoding mistake: %d, %v, %v => %d", len(b), signed, negative, n))
}
w.data.WriteByte(byte(n))
w.data.Write(b)
}
// mpfloat exports a multi-precision floating point number.
//
// The number's value is decomposed into mantissa × 2**exponent, where
// mantissa is an integer. The value is written out as mantissa (as a
// multi-precision integer) and then the exponent, except exponent is
// omitted if mantissa is zero.
func (w *exportWriter) mpfloat(f *big.Float, typ types.Type) {
if f.IsInf() {
panic("infinite constant")
}
// Break into f = mant × 2**exp, with 0.5 <= mant < 1.
var mant big.Float
exp := int64(f.MantExp(&mant))
// Scale so that mant is an integer.
prec := mant.MinPrec()
mant.SetMantExp(&mant, int(prec))
exp -= int64(prec)
manti, acc := mant.Int(nil)
if acc != big.Exact {
panic(internalErrorf("mantissa scaling failed for %f (%s)", f, acc))
}
w.mpint(manti, typ)
if manti.Sign() != 0 {
w.int64(exp)
}
}
func (w *exportWriter) bool(b bool) bool {
var x uint64
if b {
x = 1
}
w.uint64(x)
return b
}
func (w *exportWriter) int64(x int64) { w.data.int64(x) }
func (w *exportWriter) uint64(x uint64) { w.data.uint64(x) }
func (w *exportWriter) string(s string) { w.uint64(w.p.stringOff(s)) }
func (w *exportWriter) localIdent(obj types.Object) {
// Anonymous parameters.
if obj == nil {
w.string("")
return
}
name := obj.Name()
if name == "_" {
w.string("_")
return
}
w.string(name)
}
type intWriter struct {
bytes.Buffer
}
func (w *intWriter) int64(x int64) {
var buf [binary.MaxVarintLen64]byte
n := binary.PutVarint(buf[:], x)
w.Write(buf[:n])
}
func (w *intWriter) uint64(x uint64) {
var buf [binary.MaxVarintLen64]byte
n := binary.PutUvarint(buf[:], x)
w.Write(buf[:n])
}
func assert(cond bool) {
if !cond {
panic("internal error: assertion failed")
}
}
// The below is copied from go/src/cmd/compile/internal/gc/syntax.go.
// objQueue is a FIFO queue of types.Object. The zero value of objQueue is
// a ready-to-use empty queue.
type objQueue struct {
ring []types.Object
head, tail int
}
// empty returns true if q contains no Nodes.
func (q *objQueue) empty() bool {
return q.head == q.tail
}
// pushTail appends n to the tail of the queue.
func (q *objQueue) pushTail(obj types.Object) {
if len(q.ring) == 0 {
q.ring = make([]types.Object, 16)
} else if q.head+len(q.ring) == q.tail {
// Grow the ring.
nring := make([]types.Object, len(q.ring)*2)
// Copy the old elements.
part := q.ring[q.head%len(q.ring):]
if q.tail-q.head <= len(part) {
part = part[:q.tail-q.head]
copy(nring, part)
} else {
pos := copy(nring, part)
copy(nring[pos:], q.ring[:q.tail%len(q.ring)])
}
q.ring, q.head, q.tail = nring, 0, q.tail-q.head
}
q.ring[q.tail%len(q.ring)] = obj
q.tail++
}
// popHead pops a node from the head of the queue. It panics if q is empty.
func (q *objQueue) popHead() types.Object {
if q.empty() {
panic("dequeue empty")
}
obj := q.ring[q.head%len(q.ring)]
q.head++
return obj
}

2
vendor/modules.txt vendored

@ -10,7 +10,7 @@ github.com/davecgh/go-spew/spew
github.com/fatih/color
# github.com/fsnotify/fsnotify v1.4.7
github.com/fsnotify/fsnotify
# github.com/go-critic/go-critic v0.0.0-20181204210945-0af0999fabfb
# github.com/go-critic/go-critic v0.0.0-20181204210945-ee9bf5809ead
github.com/go-critic/go-critic/checkers
github.com/go-critic/go-critic/checkers/internal/lintutil
# github.com/go-lintpack/lintpack v0.5.2