full diff: https://github.com/dominikh/go-tools/compare/2019.2.3...2020.1.3 Also updates tests to accomodate updated rules: --- FAIL: TestSourcesFromTestdataWithIssuesDir/staticcheck.go (0.43s) linters_test.go:137: [run --disable-all --print-issued-lines=false --print-linter-name=false --out-format=line-number --max-same-issues=10 -Estaticcheck --no-config testdata/staticcheck.go] linters_test.go:33: Error Trace: linters_test.go:33 linters_test.go:138 linters_test.go:53 Error: Received unexpected error: staticcheck.go:11: no match for `self-assignment of x to x` vs ["SA4006: this value of `x` is never used"] in: staticcheck.go:11:2: SA4006: this value of `x` is never used unmatched errors staticcheck.go:11:2: SA4006: this value of `x` is never used Test: TestSourcesFromTestdataWithIssuesDir/staticcheck.go Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
		
			
				
	
	
		
			915 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			915 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package stylecheck // import "honnef.co/go/tools/stylecheck"
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"go/ast"
 | 
						|
	"go/constant"
 | 
						|
	"go/token"
 | 
						|
	"go/types"
 | 
						|
	"sort"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"unicode"
 | 
						|
	"unicode/utf8"
 | 
						|
 | 
						|
	"honnef.co/go/tools/code"
 | 
						|
	"honnef.co/go/tools/config"
 | 
						|
	"honnef.co/go/tools/edit"
 | 
						|
	"honnef.co/go/tools/internal/passes/buildir"
 | 
						|
	"honnef.co/go/tools/ir"
 | 
						|
	. "honnef.co/go/tools/lint/lintdsl"
 | 
						|
	"honnef.co/go/tools/pattern"
 | 
						|
	"honnef.co/go/tools/report"
 | 
						|
 | 
						|
	"golang.org/x/tools/go/analysis"
 | 
						|
	"golang.org/x/tools/go/analysis/passes/inspect"
 | 
						|
	"golang.org/x/tools/go/ast/inspector"
 | 
						|
	"golang.org/x/tools/go/types/typeutil"
 | 
						|
)
 | 
						|
 | 
						|
func CheckPackageComment(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	// - At least one file in a non-main package should have a package comment
 | 
						|
	//
 | 
						|
	// - The comment should be of the form
 | 
						|
	// "Package x ...". This has a slight potential for false
 | 
						|
	// positives, as multiple files can have package comments, in
 | 
						|
	// which case they get appended. But that doesn't happen a lot in
 | 
						|
	// the real world.
 | 
						|
 | 
						|
	if pass.Pkg.Name() == "main" {
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
	hasDocs := false
 | 
						|
	for _, f := range pass.Files {
 | 
						|
		if code.IsInTest(pass, f) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if f.Doc != nil && len(f.Doc.List) > 0 {
 | 
						|
			hasDocs = true
 | 
						|
			prefix := "Package " + f.Name.Name + " "
 | 
						|
			if !strings.HasPrefix(strings.TrimSpace(f.Doc.Text()), prefix) {
 | 
						|
				report.Report(pass, f.Doc, fmt.Sprintf(`package comment should be of the form "%s..."`, prefix))
 | 
						|
			}
 | 
						|
			f.Doc.Text()
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if !hasDocs {
 | 
						|
		for _, f := range pass.Files {
 | 
						|
			if code.IsInTest(pass, f) {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			report.Report(pass, f, "at least one file in a package should have a package comment", report.ShortRange())
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
func CheckDotImports(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	for _, f := range pass.Files {
 | 
						|
	imports:
 | 
						|
		for _, imp := range f.Imports {
 | 
						|
			path := imp.Path.Value
 | 
						|
			path = path[1 : len(path)-1]
 | 
						|
			for _, w := range config.For(pass).DotImportWhitelist {
 | 
						|
				if w == path {
 | 
						|
					continue imports
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if imp.Name != nil && imp.Name.Name == "." && !code.IsInTest(pass, f) {
 | 
						|
				report.Report(pass, imp, "should not use dot imports", report.FilterGenerated())
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
func CheckDuplicatedImports(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	for _, f := range pass.Files {
 | 
						|
		// Collect all imports by their import path
 | 
						|
		imports := make(map[string][]*ast.ImportSpec, len(f.Imports))
 | 
						|
		for _, imp := range f.Imports {
 | 
						|
			imports[imp.Path.Value] = append(imports[imp.Path.Value], imp)
 | 
						|
		}
 | 
						|
 | 
						|
		for path, value := range imports {
 | 
						|
			if path[1:len(path)-1] == "unsafe" {
 | 
						|
				// Don't flag unsafe. Cgo generated code imports
 | 
						|
				// unsafe using the blank identifier, and most
 | 
						|
				// user-written cgo code also imports unsafe
 | 
						|
				// explicitly.
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			// If there's more than one import per path, we flag that
 | 
						|
			if len(value) > 1 {
 | 
						|
				s := fmt.Sprintf("package %s is being imported more than once", path)
 | 
						|
				opts := []report.Option{report.FilterGenerated()}
 | 
						|
				for _, imp := range value[1:] {
 | 
						|
					opts = append(opts, report.Related(imp, fmt.Sprintf("other import of %s", path)))
 | 
						|
				}
 | 
						|
				report.Report(pass, value[0], s, opts...)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
func CheckBlankImports(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	fset := pass.Fset
 | 
						|
	for _, f := range pass.Files {
 | 
						|
		if code.IsMainLike(pass) || code.IsInTest(pass, f) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Collect imports of the form `import _ "foo"`, i.e. with no
 | 
						|
		// parentheses, as their comment will be associated with the
 | 
						|
		// (paren-free) GenDecl, not the import spec itself.
 | 
						|
		//
 | 
						|
		// We don't directly process the GenDecl so that we can
 | 
						|
		// correctly handle the following:
 | 
						|
		//
 | 
						|
		//  import _ "foo"
 | 
						|
		//  import _ "bar"
 | 
						|
		//
 | 
						|
		// where only the first import should get flagged.
 | 
						|
		skip := map[ast.Spec]bool{}
 | 
						|
		ast.Inspect(f, func(node ast.Node) bool {
 | 
						|
			switch node := node.(type) {
 | 
						|
			case *ast.File:
 | 
						|
				return true
 | 
						|
			case *ast.GenDecl:
 | 
						|
				if node.Tok != token.IMPORT {
 | 
						|
					return false
 | 
						|
				}
 | 
						|
				if node.Lparen == token.NoPos && node.Doc != nil {
 | 
						|
					skip[node.Specs[0]] = true
 | 
						|
				}
 | 
						|
				return false
 | 
						|
			}
 | 
						|
			return false
 | 
						|
		})
 | 
						|
		for i, imp := range f.Imports {
 | 
						|
			pos := fset.Position(imp.Pos())
 | 
						|
 | 
						|
			if !code.IsBlank(imp.Name) {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			// Only flag the first blank import in a group of imports,
 | 
						|
			// or don't flag any of them, if the first one is
 | 
						|
			// commented
 | 
						|
			if i > 0 {
 | 
						|
				prev := f.Imports[i-1]
 | 
						|
				prevPos := fset.Position(prev.Pos())
 | 
						|
				if pos.Line-1 == prevPos.Line && code.IsBlank(prev.Name) {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if imp.Doc == nil && imp.Comment == nil && !skip[imp] {
 | 
						|
				report.Report(pass, imp, "a blank import should be only in a main or test package, or have a comment justifying it")
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
func CheckIncDec(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	// TODO(dh): this can be noisy for function bodies that look like this:
 | 
						|
	// 	x += 3
 | 
						|
	// 	...
 | 
						|
	// 	x += 2
 | 
						|
	// 	...
 | 
						|
	// 	x += 1
 | 
						|
	fn := func(node ast.Node) {
 | 
						|
		assign := node.(*ast.AssignStmt)
 | 
						|
		if assign.Tok != token.ADD_ASSIGN && assign.Tok != token.SUB_ASSIGN {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		if (len(assign.Lhs) != 1 || len(assign.Rhs) != 1) ||
 | 
						|
			!code.IsIntLiteral(assign.Rhs[0], "1") {
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		suffix := ""
 | 
						|
		switch assign.Tok {
 | 
						|
		case token.ADD_ASSIGN:
 | 
						|
			suffix = "++"
 | 
						|
		case token.SUB_ASSIGN:
 | 
						|
			suffix = "--"
 | 
						|
		}
 | 
						|
 | 
						|
		report.Report(pass, assign, fmt.Sprintf("should replace %s with %s%s", report.Render(pass, assign), report.Render(pass, assign.Lhs[0]), suffix))
 | 
						|
	}
 | 
						|
	code.Preorder(pass, fn, (*ast.AssignStmt)(nil))
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
func CheckErrorReturn(pass *analysis.Pass) (interface{}, error) {
 | 
						|
fnLoop:
 | 
						|
	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
 | 
						|
		sig := fn.Type().(*types.Signature)
 | 
						|
		rets := sig.Results()
 | 
						|
		if rets == nil || rets.Len() < 2 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		if rets.At(rets.Len()-1).Type() == types.Universe.Lookup("error").Type() {
 | 
						|
			// Last return type is error. If the function also returns
 | 
						|
			// errors in other positions, that's fine.
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		for i := rets.Len() - 2; i >= 0; i-- {
 | 
						|
			if rets.At(i).Type() == types.Universe.Lookup("error").Type() {
 | 
						|
				report.Report(pass, rets.At(i), "error should be returned as the last argument", report.ShortRange())
 | 
						|
				continue fnLoop
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
// CheckUnexportedReturn checks that exported functions on exported
 | 
						|
// types do not return unexported types.
 | 
						|
func CheckUnexportedReturn(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
 | 
						|
		if fn.Synthetic != "" || fn.Parent() != nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if !ast.IsExported(fn.Name()) || code.IsMain(pass) || code.IsInTest(pass, fn) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		sig := fn.Type().(*types.Signature)
 | 
						|
		if sig.Recv() != nil && !ast.IsExported(code.Dereference(sig.Recv().Type()).(*types.Named).Obj().Name()) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		res := sig.Results()
 | 
						|
		for i := 0; i < res.Len(); i++ {
 | 
						|
			if named, ok := code.DereferenceR(res.At(i).Type()).(*types.Named); ok &&
 | 
						|
				!ast.IsExported(named.Obj().Name()) &&
 | 
						|
				named != types.Universe.Lookup("error").Type() {
 | 
						|
				report.Report(pass, fn, "should not return unexported type")
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
func CheckReceiverNames(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
 | 
						|
	for _, m := range irpkg.Members {
 | 
						|
		if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
 | 
						|
			ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
 | 
						|
			for _, sel := range ms {
 | 
						|
				fn := sel.Obj().(*types.Func)
 | 
						|
				recv := fn.Type().(*types.Signature).Recv()
 | 
						|
				if code.Dereference(recv.Type()) != T.Type() {
 | 
						|
					// skip embedded methods
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				if recv.Name() == "self" || recv.Name() == "this" {
 | 
						|
					report.Report(pass, recv, `receiver name should be a reflection of its identity; don't use generic names such as "this" or "self"`, report.FilterGenerated())
 | 
						|
				}
 | 
						|
				if recv.Name() == "_" {
 | 
						|
					report.Report(pass, recv, "receiver name should not be an underscore, omit the name if it is unused", report.FilterGenerated())
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
func CheckReceiverNamesIdentical(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
 | 
						|
	for _, m := range irpkg.Members {
 | 
						|
		names := map[string]int{}
 | 
						|
 | 
						|
		var firstFn *types.Func
 | 
						|
		if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
 | 
						|
			ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
 | 
						|
			for _, sel := range ms {
 | 
						|
				fn := sel.Obj().(*types.Func)
 | 
						|
				recv := fn.Type().(*types.Signature).Recv()
 | 
						|
				if code.IsGenerated(pass, recv.Pos()) {
 | 
						|
					// Don't concern ourselves with methods in generated code
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				if code.Dereference(recv.Type()) != T.Type() {
 | 
						|
					// skip embedded methods
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				if firstFn == nil {
 | 
						|
					firstFn = fn
 | 
						|
				}
 | 
						|
				if recv.Name() != "" && recv.Name() != "_" {
 | 
						|
					names[recv.Name()]++
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if len(names) > 1 {
 | 
						|
			var seen []string
 | 
						|
			for name, count := range names {
 | 
						|
				seen = append(seen, fmt.Sprintf("%dx %q", count, name))
 | 
						|
			}
 | 
						|
			sort.Strings(seen)
 | 
						|
 | 
						|
			report.Report(pass, firstFn, fmt.Sprintf("methods on the same type should have the same receiver name (seen %s)", strings.Join(seen, ", ")))
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
func CheckContextFirstArg(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	// TODO(dh): this check doesn't apply to test helpers. Example from the stdlib:
 | 
						|
	// 	func helperCommandContext(t *testing.T, ctx context.Context, s ...string) (cmd *exec.Cmd) {
 | 
						|
fnLoop:
 | 
						|
	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
 | 
						|
		if fn.Synthetic != "" || fn.Parent() != nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		params := fn.Signature.Params()
 | 
						|
		if params.Len() < 2 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if types.TypeString(params.At(0).Type(), nil) == "context.Context" {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		for i := 1; i < params.Len(); i++ {
 | 
						|
			param := params.At(i)
 | 
						|
			if types.TypeString(param.Type(), nil) == "context.Context" {
 | 
						|
				report.Report(pass, param, "context.Context should be the first argument of a function", report.ShortRange())
 | 
						|
				continue fnLoop
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
func CheckErrorStrings(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	objNames := map[*ir.Package]map[string]bool{}
 | 
						|
	irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
 | 
						|
	objNames[irpkg] = map[string]bool{}
 | 
						|
	for _, m := range irpkg.Members {
 | 
						|
		if typ, ok := m.(*ir.Type); ok {
 | 
						|
			objNames[irpkg][typ.Name()] = true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
 | 
						|
		objNames[fn.Package()][fn.Name()] = true
 | 
						|
	}
 | 
						|
 | 
						|
	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
 | 
						|
		if code.IsInTest(pass, fn) {
 | 
						|
			// We don't care about malformed error messages in tests;
 | 
						|
			// they're usually for direct human consumption, not part
 | 
						|
			// of an API
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		for _, block := range fn.Blocks {
 | 
						|
		instrLoop:
 | 
						|
			for _, ins := range block.Instrs {
 | 
						|
				call, ok := ins.(*ir.Call)
 | 
						|
				if !ok {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				if !code.IsCallToAny(call.Common(), "errors.New", "fmt.Errorf") {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
 | 
						|
				k, ok := call.Common().Args[0].(*ir.Const)
 | 
						|
				if !ok {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
 | 
						|
				s := constant.StringVal(k.Value)
 | 
						|
				if len(s) == 0 {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				switch s[len(s)-1] {
 | 
						|
				case '.', ':', '!', '\n':
 | 
						|
					report.Report(pass, call, "error strings should not end with punctuation or a newline")
 | 
						|
				}
 | 
						|
				idx := strings.IndexByte(s, ' ')
 | 
						|
				if idx == -1 {
 | 
						|
					// single word error message, probably not a real
 | 
						|
					// error but something used in tests or during
 | 
						|
					// debugging
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				word := s[:idx]
 | 
						|
				first, n := utf8.DecodeRuneInString(word)
 | 
						|
				if !unicode.IsUpper(first) {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				for _, c := range word[n:] {
 | 
						|
					if unicode.IsUpper(c) {
 | 
						|
						// Word is probably an initialism or
 | 
						|
						// multi-word function name
 | 
						|
						continue instrLoop
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				word = strings.TrimRightFunc(word, func(r rune) bool { return unicode.IsPunct(r) })
 | 
						|
				if objNames[fn.Package()][word] {
 | 
						|
					// Word is probably the name of a function or type in this package
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				// First word in error starts with a capital
 | 
						|
				// letter, and the word doesn't contain any other
 | 
						|
				// capitals, making it unlikely to be an
 | 
						|
				// initialism or multi-word function name.
 | 
						|
				//
 | 
						|
				// It could still be a proper noun, though.
 | 
						|
 | 
						|
				report.Report(pass, call, "error strings should not be capitalized")
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
func CheckTimeNames(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	suffixes := []string{
 | 
						|
		"Sec", "Secs", "Seconds",
 | 
						|
		"Msec", "Msecs",
 | 
						|
		"Milli", "Millis", "Milliseconds",
 | 
						|
		"Usec", "Usecs", "Microseconds",
 | 
						|
		"MS", "Ms",
 | 
						|
	}
 | 
						|
	fn := func(names []*ast.Ident) {
 | 
						|
		for _, name := range names {
 | 
						|
			if _, ok := pass.TypesInfo.Defs[name]; !ok {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			T := pass.TypesInfo.TypeOf(name)
 | 
						|
			if !code.IsType(T, "time.Duration") && !code.IsType(T, "*time.Duration") {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			for _, suffix := range suffixes {
 | 
						|
				if strings.HasSuffix(name.Name, suffix) {
 | 
						|
					report.Report(pass, name, fmt.Sprintf("var %s is of type %v; don't use unit-specific suffix %q", name.Name, T, suffix))
 | 
						|
					break
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	fn2 := func(node ast.Node) {
 | 
						|
		switch node := node.(type) {
 | 
						|
		case *ast.ValueSpec:
 | 
						|
			fn(node.Names)
 | 
						|
		case *ast.FieldList:
 | 
						|
			for _, field := range node.List {
 | 
						|
				fn(field.Names)
 | 
						|
			}
 | 
						|
		case *ast.AssignStmt:
 | 
						|
			if node.Tok != token.DEFINE {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			var names []*ast.Ident
 | 
						|
			for _, lhs := range node.Lhs {
 | 
						|
				if lhs, ok := lhs.(*ast.Ident); ok {
 | 
						|
					names = append(names, lhs)
 | 
						|
				}
 | 
						|
			}
 | 
						|
			fn(names)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	code.Preorder(pass, fn2, (*ast.ValueSpec)(nil), (*ast.FieldList)(nil), (*ast.AssignStmt)(nil))
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
func CheckErrorVarNames(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	for _, f := range pass.Files {
 | 
						|
		for _, decl := range f.Decls {
 | 
						|
			gen, ok := decl.(*ast.GenDecl)
 | 
						|
			if !ok || gen.Tok != token.VAR {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			for _, spec := range gen.Specs {
 | 
						|
				spec := spec.(*ast.ValueSpec)
 | 
						|
				if len(spec.Names) != len(spec.Values) {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
 | 
						|
				for i, name := range spec.Names {
 | 
						|
					val := spec.Values[i]
 | 
						|
					if !code.IsCallToAnyAST(pass, val, "errors.New", "fmt.Errorf") {
 | 
						|
						continue
 | 
						|
					}
 | 
						|
 | 
						|
					if pass.Pkg.Path() == "net/http" && strings.HasPrefix(name.Name, "http2err") {
 | 
						|
						// special case for internal variable names of
 | 
						|
						// bundled HTTP 2 code in net/http
 | 
						|
						continue
 | 
						|
					}
 | 
						|
					prefix := "err"
 | 
						|
					if name.IsExported() {
 | 
						|
						prefix = "Err"
 | 
						|
					}
 | 
						|
					if !strings.HasPrefix(name.Name, prefix) {
 | 
						|
						report.Report(pass, name, fmt.Sprintf("error var %s should have name of the form %sFoo", name.Name, prefix))
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
var httpStatusCodes = map[int]string{
 | 
						|
	100: "StatusContinue",
 | 
						|
	101: "StatusSwitchingProtocols",
 | 
						|
	102: "StatusProcessing",
 | 
						|
	200: "StatusOK",
 | 
						|
	201: "StatusCreated",
 | 
						|
	202: "StatusAccepted",
 | 
						|
	203: "StatusNonAuthoritativeInfo",
 | 
						|
	204: "StatusNoContent",
 | 
						|
	205: "StatusResetContent",
 | 
						|
	206: "StatusPartialContent",
 | 
						|
	207: "StatusMultiStatus",
 | 
						|
	208: "StatusAlreadyReported",
 | 
						|
	226: "StatusIMUsed",
 | 
						|
	300: "StatusMultipleChoices",
 | 
						|
	301: "StatusMovedPermanently",
 | 
						|
	302: "StatusFound",
 | 
						|
	303: "StatusSeeOther",
 | 
						|
	304: "StatusNotModified",
 | 
						|
	305: "StatusUseProxy",
 | 
						|
	307: "StatusTemporaryRedirect",
 | 
						|
	308: "StatusPermanentRedirect",
 | 
						|
	400: "StatusBadRequest",
 | 
						|
	401: "StatusUnauthorized",
 | 
						|
	402: "StatusPaymentRequired",
 | 
						|
	403: "StatusForbidden",
 | 
						|
	404: "StatusNotFound",
 | 
						|
	405: "StatusMethodNotAllowed",
 | 
						|
	406: "StatusNotAcceptable",
 | 
						|
	407: "StatusProxyAuthRequired",
 | 
						|
	408: "StatusRequestTimeout",
 | 
						|
	409: "StatusConflict",
 | 
						|
	410: "StatusGone",
 | 
						|
	411: "StatusLengthRequired",
 | 
						|
	412: "StatusPreconditionFailed",
 | 
						|
	413: "StatusRequestEntityTooLarge",
 | 
						|
	414: "StatusRequestURITooLong",
 | 
						|
	415: "StatusUnsupportedMediaType",
 | 
						|
	416: "StatusRequestedRangeNotSatisfiable",
 | 
						|
	417: "StatusExpectationFailed",
 | 
						|
	418: "StatusTeapot",
 | 
						|
	422: "StatusUnprocessableEntity",
 | 
						|
	423: "StatusLocked",
 | 
						|
	424: "StatusFailedDependency",
 | 
						|
	426: "StatusUpgradeRequired",
 | 
						|
	428: "StatusPreconditionRequired",
 | 
						|
	429: "StatusTooManyRequests",
 | 
						|
	431: "StatusRequestHeaderFieldsTooLarge",
 | 
						|
	451: "StatusUnavailableForLegalReasons",
 | 
						|
	500: "StatusInternalServerError",
 | 
						|
	501: "StatusNotImplemented",
 | 
						|
	502: "StatusBadGateway",
 | 
						|
	503: "StatusServiceUnavailable",
 | 
						|
	504: "StatusGatewayTimeout",
 | 
						|
	505: "StatusHTTPVersionNotSupported",
 | 
						|
	506: "StatusVariantAlsoNegotiates",
 | 
						|
	507: "StatusInsufficientStorage",
 | 
						|
	508: "StatusLoopDetected",
 | 
						|
	510: "StatusNotExtended",
 | 
						|
	511: "StatusNetworkAuthenticationRequired",
 | 
						|
}
 | 
						|
 | 
						|
func CheckHTTPStatusCodes(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	whitelist := map[string]bool{}
 | 
						|
	for _, code := range config.For(pass).HTTPStatusCodeWhitelist {
 | 
						|
		whitelist[code] = true
 | 
						|
	}
 | 
						|
	fn := func(node ast.Node) {
 | 
						|
		call := node.(*ast.CallExpr)
 | 
						|
 | 
						|
		var arg int
 | 
						|
		switch code.CallNameAST(pass, call) {
 | 
						|
		case "net/http.Error":
 | 
						|
			arg = 2
 | 
						|
		case "net/http.Redirect":
 | 
						|
			arg = 3
 | 
						|
		case "net/http.StatusText":
 | 
						|
			arg = 0
 | 
						|
		case "net/http.RedirectHandler":
 | 
						|
			arg = 1
 | 
						|
		default:
 | 
						|
			return
 | 
						|
		}
 | 
						|
		lit, ok := call.Args[arg].(*ast.BasicLit)
 | 
						|
		if !ok {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		if whitelist[lit.Value] {
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		n, err := strconv.Atoi(lit.Value)
 | 
						|
		if err != nil {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		s, ok := httpStatusCodes[n]
 | 
						|
		if !ok {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		report.Report(pass, lit, fmt.Sprintf("should use constant http.%s instead of numeric literal %d", s, n),
 | 
						|
			report.FilterGenerated(),
 | 
						|
			report.Fixes(edit.Fix(fmt.Sprintf("use http.%s instead of %d", s, n), edit.ReplaceWithString(pass.Fset, lit, "http."+s))))
 | 
						|
	}
 | 
						|
	code.Preorder(pass, fn, (*ast.CallExpr)(nil))
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
func CheckDefaultCaseOrder(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	fn := func(node ast.Node) {
 | 
						|
		stmt := node.(*ast.SwitchStmt)
 | 
						|
		list := stmt.Body.List
 | 
						|
		for i, c := range list {
 | 
						|
			if c.(*ast.CaseClause).List == nil && i != 0 && i != len(list)-1 {
 | 
						|
				report.Report(pass, c, "default case should be first or last in switch statement", report.FilterGenerated())
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	code.Preorder(pass, fn, (*ast.SwitchStmt)(nil))
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
var (
 | 
						|
	checkYodaConditionsQ = pattern.MustParse(`(BinaryExpr left@(BasicLit _ _) tok@(Or "==" "!=") right@(Not (BasicLit _ _)))`)
 | 
						|
	checkYodaConditionsR = pattern.MustParse(`(BinaryExpr right tok left)`)
 | 
						|
)
 | 
						|
 | 
						|
func CheckYodaConditions(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	fn := func(node ast.Node) {
 | 
						|
		if _, edits, ok := MatchAndEdit(pass, checkYodaConditionsQ, checkYodaConditionsR, node); ok {
 | 
						|
			report.Report(pass, node, "don't use Yoda conditions",
 | 
						|
				report.FilterGenerated(),
 | 
						|
				report.Fixes(edit.Fix("un-Yoda-fy", edits...)))
 | 
						|
		}
 | 
						|
	}
 | 
						|
	code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
func CheckInvisibleCharacters(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	fn := func(node ast.Node) {
 | 
						|
		lit := node.(*ast.BasicLit)
 | 
						|
		if lit.Kind != token.STRING {
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		type invalid struct {
 | 
						|
			r   rune
 | 
						|
			off int
 | 
						|
		}
 | 
						|
		var invalids []invalid
 | 
						|
		hasFormat := false
 | 
						|
		hasControl := false
 | 
						|
		for off, r := range lit.Value {
 | 
						|
			if unicode.Is(unicode.Cf, r) {
 | 
						|
				invalids = append(invalids, invalid{r, off})
 | 
						|
				hasFormat = true
 | 
						|
			} else if unicode.Is(unicode.Cc, r) && r != '\n' && r != '\t' && r != '\r' {
 | 
						|
				invalids = append(invalids, invalid{r, off})
 | 
						|
				hasControl = true
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		switch len(invalids) {
 | 
						|
		case 0:
 | 
						|
			return
 | 
						|
		case 1:
 | 
						|
			var kind string
 | 
						|
			if hasFormat {
 | 
						|
				kind = "format"
 | 
						|
			} else if hasControl {
 | 
						|
				kind = "control"
 | 
						|
			} else {
 | 
						|
				panic("unreachable")
 | 
						|
			}
 | 
						|
 | 
						|
			r := invalids[0]
 | 
						|
			msg := fmt.Sprintf("string literal contains the Unicode %s character %U, consider using the %q escape sequence instead", kind, r.r, r.r)
 | 
						|
 | 
						|
			replacement := strconv.QuoteRune(r.r)
 | 
						|
			replacement = replacement[1 : len(replacement)-1]
 | 
						|
			edit := analysis.SuggestedFix{
 | 
						|
				Message: fmt.Sprintf("replace %s character %U with %q", kind, r.r, r.r),
 | 
						|
				TextEdits: []analysis.TextEdit{{
 | 
						|
					Pos:     lit.Pos() + token.Pos(r.off),
 | 
						|
					End:     lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
 | 
						|
					NewText: []byte(replacement),
 | 
						|
				}},
 | 
						|
			}
 | 
						|
			delete := analysis.SuggestedFix{
 | 
						|
				Message: fmt.Sprintf("delete %s character %U", kind, r),
 | 
						|
				TextEdits: []analysis.TextEdit{{
 | 
						|
					Pos: lit.Pos() + token.Pos(r.off),
 | 
						|
					End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
 | 
						|
				}},
 | 
						|
			}
 | 
						|
			report.Report(pass, lit, msg, report.Fixes(edit, delete))
 | 
						|
		default:
 | 
						|
			var kind string
 | 
						|
			if hasFormat && hasControl {
 | 
						|
				kind = "format and control"
 | 
						|
			} else if hasFormat {
 | 
						|
				kind = "format"
 | 
						|
			} else if hasControl {
 | 
						|
				kind = "control"
 | 
						|
			} else {
 | 
						|
				panic("unreachable")
 | 
						|
			}
 | 
						|
 | 
						|
			msg := fmt.Sprintf("string literal contains Unicode %s characters, consider using escape sequences instead", kind)
 | 
						|
			var edits []analysis.TextEdit
 | 
						|
			var deletions []analysis.TextEdit
 | 
						|
			for _, r := range invalids {
 | 
						|
				replacement := strconv.QuoteRune(r.r)
 | 
						|
				replacement = replacement[1 : len(replacement)-1]
 | 
						|
				edits = append(edits, analysis.TextEdit{
 | 
						|
					Pos:     lit.Pos() + token.Pos(r.off),
 | 
						|
					End:     lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
 | 
						|
					NewText: []byte(replacement),
 | 
						|
				})
 | 
						|
				deletions = append(deletions, analysis.TextEdit{
 | 
						|
					Pos: lit.Pos() + token.Pos(r.off),
 | 
						|
					End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
 | 
						|
				})
 | 
						|
			}
 | 
						|
			edit := analysis.SuggestedFix{
 | 
						|
				Message:   fmt.Sprintf("replace all %s characters with escape sequences", kind),
 | 
						|
				TextEdits: edits,
 | 
						|
			}
 | 
						|
			delete := analysis.SuggestedFix{
 | 
						|
				Message:   fmt.Sprintf("delete all %s characters", kind),
 | 
						|
				TextEdits: deletions,
 | 
						|
			}
 | 
						|
			report.Report(pass, lit, msg, report.Fixes(edit, delete))
 | 
						|
		}
 | 
						|
	}
 | 
						|
	code.Preorder(pass, fn, (*ast.BasicLit)(nil))
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
func CheckExportedFunctionDocs(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	fn := func(node ast.Node) {
 | 
						|
		if code.IsInTest(pass, node) {
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		decl := node.(*ast.FuncDecl)
 | 
						|
		if decl.Doc == nil {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		if !ast.IsExported(decl.Name.Name) {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		kind := "function"
 | 
						|
		if decl.Recv != nil {
 | 
						|
			kind = "method"
 | 
						|
			switch T := decl.Recv.List[0].Type.(type) {
 | 
						|
			case *ast.StarExpr:
 | 
						|
				if !ast.IsExported(T.X.(*ast.Ident).Name) {
 | 
						|
					return
 | 
						|
				}
 | 
						|
			case *ast.Ident:
 | 
						|
				if !ast.IsExported(T.Name) {
 | 
						|
					return
 | 
						|
				}
 | 
						|
			default:
 | 
						|
				ExhaustiveTypeSwitch(T)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		prefix := decl.Name.Name + " "
 | 
						|
		if !strings.HasPrefix(decl.Doc.Text(), prefix) {
 | 
						|
			report.Report(pass, decl.Doc, fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, decl.Name.Name, prefix), report.FilterGenerated())
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	code.Preorder(pass, fn, (*ast.FuncDecl)(nil))
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
func CheckExportedTypeDocs(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	var genDecl *ast.GenDecl
 | 
						|
	fn := func(node ast.Node, push bool) bool {
 | 
						|
		if !push {
 | 
						|
			genDecl = nil
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		if code.IsInTest(pass, node) {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
 | 
						|
		switch node := node.(type) {
 | 
						|
		case *ast.GenDecl:
 | 
						|
			if node.Tok == token.IMPORT {
 | 
						|
				return false
 | 
						|
			}
 | 
						|
			genDecl = node
 | 
						|
			return true
 | 
						|
		case *ast.TypeSpec:
 | 
						|
			if !ast.IsExported(node.Name.Name) {
 | 
						|
				return false
 | 
						|
			}
 | 
						|
 | 
						|
			doc := node.Doc
 | 
						|
			if doc == nil {
 | 
						|
				if len(genDecl.Specs) != 1 {
 | 
						|
					// more than one spec in the GenDecl, don't validate the
 | 
						|
					// docstring
 | 
						|
					return false
 | 
						|
				}
 | 
						|
				if genDecl.Lparen.IsValid() {
 | 
						|
					// 'type ( T )' is weird, don't guess the user's intention
 | 
						|
					return false
 | 
						|
				}
 | 
						|
				doc = genDecl.Doc
 | 
						|
				if doc == nil {
 | 
						|
					return false
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			s := doc.Text()
 | 
						|
			articles := [...]string{"A", "An", "The"}
 | 
						|
			for _, a := range articles {
 | 
						|
				if strings.HasPrefix(s, a+" ") {
 | 
						|
					s = s[len(a)+1:]
 | 
						|
					break
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if !strings.HasPrefix(s, node.Name.Name+" ") {
 | 
						|
				report.Report(pass, doc, fmt.Sprintf(`comment on exported type %s should be of the form "%s ..." (with optional leading article)`, node.Name.Name, node.Name.Name), report.FilterGenerated())
 | 
						|
			}
 | 
						|
			return false
 | 
						|
		case *ast.FuncLit, *ast.FuncDecl:
 | 
						|
			return false
 | 
						|
		default:
 | 
						|
			ExhaustiveTypeSwitch(node)
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.GenDecl)(nil), (*ast.TypeSpec)(nil), (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)}, fn)
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
func CheckExportedVarDocs(pass *analysis.Pass) (interface{}, error) {
 | 
						|
	var genDecl *ast.GenDecl
 | 
						|
	fn := func(node ast.Node, push bool) bool {
 | 
						|
		if !push {
 | 
						|
			genDecl = nil
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		if code.IsInTest(pass, node) {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
 | 
						|
		switch node := node.(type) {
 | 
						|
		case *ast.GenDecl:
 | 
						|
			if node.Tok == token.IMPORT {
 | 
						|
				return false
 | 
						|
			}
 | 
						|
			genDecl = node
 | 
						|
			return true
 | 
						|
		case *ast.ValueSpec:
 | 
						|
			if genDecl.Lparen.IsValid() || len(node.Names) > 1 {
 | 
						|
				// Don't try to guess the user's intention
 | 
						|
				return false
 | 
						|
			}
 | 
						|
			name := node.Names[0].Name
 | 
						|
			if !ast.IsExported(name) {
 | 
						|
				return false
 | 
						|
			}
 | 
						|
			if genDecl.Doc == nil {
 | 
						|
				return false
 | 
						|
			}
 | 
						|
			prefix := name + " "
 | 
						|
			if !strings.HasPrefix(genDecl.Doc.Text(), prefix) {
 | 
						|
				kind := "var"
 | 
						|
				if genDecl.Tok == token.CONST {
 | 
						|
					kind = "const"
 | 
						|
				}
 | 
						|
				report.Report(pass, genDecl.Doc, fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix), report.FilterGenerated())
 | 
						|
			}
 | 
						|
			return false
 | 
						|
		case *ast.FuncLit, *ast.FuncDecl:
 | 
						|
			return false
 | 
						|
		default:
 | 
						|
			ExhaustiveTypeSwitch(node)
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.GenDecl)(nil), (*ast.ValueSpec)(nil), (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)}, fn)
 | 
						|
	return nil, nil
 | 
						|
}
 |