1
0
mirror of https://github.com/mgechev/revive.git synced 2025-03-31 21:55:29 +02:00

Merge master

This commit is contained in:
mgechev 2018-10-11 14:55:17 -07:00
commit 69f012a7d9
No known key found for this signature in database
GPG Key ID: A98CD29F2650FAD2
57 changed files with 706 additions and 73 deletions

12
PULL_REQUEST_TEMPLATE.md Normal file
View File

@ -0,0 +1,12 @@
<!-- ### IMPORTANT ### -->
<!-- Please do not create a Pull Request without creating an issue first.** -->
<!-- If you're fixing a typo or improving the documentation, you may not have to open an issue. -->
<!-- ### CHECKLIST ### -->
<!-- Please, describe in details what's your motivation for this PR -->
<!-- Did you add tests? -->
<!-- Does your code follows the coding style of the rest of the repository? -->
<!-- Does the Travis build passes? -->
<!-- ### FOOTER (OPTIONAL) ### -->
<!-- If you're closing an issue add "Closes #XXXX" in your comment. This way, the PR will be linked to the issue automatically. -->

View File

@ -41,6 +41,8 @@ Here's how `revive` is different from `golint`:
- [Custom Configuration](#custom-configuration) - [Custom Configuration](#custom-configuration)
- [Recommended Configuration](#recommended-configuration) - [Recommended Configuration](#recommended-configuration)
- [Available Rules](#available-rules) - [Available Rules](#available-rules)
- [Configurable rules](#configurable-rules)
- [`var-naming`](#var-naming)
- [Available Formatters](#available-formatters) - [Available Formatters](#available-formatters)
- [Friendly](#friendly) - [Friendly](#friendly)
- [Stylish](#stylish) - [Stylish](#stylish)
@ -51,7 +53,7 @@ Here's how `revive` is different from `golint`:
- [Custom Formatter](#custom-formatter) - [Custom Formatter](#custom-formatter)
- [Speed Comparison](#speed-comparison) - [Speed Comparison](#speed-comparison)
- [golint](#golint) - [golint](#golint)
- [revive](#revive-1) - [revive](#revive)
- [Contributors](#contributors) - [Contributors](#contributors)
- [License](#license) - [License](#license)
@ -271,6 +273,9 @@ List of all available rules. The rules ported from `golint` are left unchanged a
| `redefines-builtin-id`| n/a | Warns on redefinitions of builtin identifiers | no | no | | `redefines-builtin-id`| n/a | Warns on redefinitions of builtin identifiers | no | no |
| `function-result-limit` | int | Specifies the maximum number of results a function can return | no | no | | `function-result-limit` | int | Specifies the maximum number of results a function can return | no | no |
| `imports-blacklist` | []string | Disallows importing the specified packages | no | no | | `imports-blacklist` | []string | Disallows importing the specified packages | no | no |
| `range-val-in-closure`| n/a | Warns if range value is used in a closure dispatched as goroutine| no | no |
| `waitgroup-by-value` | n/a | Warns on functions taking sync.WaitGroup as a by-value parameter | no | no |
| `atomic` | n/a | Check for common mistaken usages of the `sync/atomic` package | no | no |
| `empty-lines` | n/a | Warns when there are heading or trailing newlines in a block | no | no | | `empty-lines` | n/a | Warns when there are heading or trailing newlines in a block | no | no |
## Configurable rules ## Configurable rules

View File

@ -67,6 +67,10 @@ var allRules = append([]lint.Rule{
&rule.ImportsBlacklistRule{}, &rule.ImportsBlacklistRule{},
&rule.FunctionResultsLimitRule{}, &rule.FunctionResultsLimitRule{},
&rule.MaxPublicStructsRule{}, &rule.MaxPublicStructsRule{},
&rule.RangeValInClosureRule{},
&rule.WaitGroupByValueRule{},
&rule.AtomicRule{},
&rule.EmptyLinesRule{},
}, defaultRules...) }, defaultRules...)
var allFormatters = []lint.Formatter{ var allFormatters = []lint.Formatter{

45
fixtures/atomic.go Normal file
View File

@ -0,0 +1,45 @@
package fixtures
import (
"sync/atomic"
)
type Counter uint64
func AtomicTests() {
x := uint64(1)
x = atomic.AddUint64(&x, 1) // MATCH /direct assignment to atomic value/
_, x = 10, atomic.AddUint64(&x, 1) // MATCH /direct assignment to atomic value/
x, _ = atomic.AddUint64(&x, 1), 10 // MATCH /direct assignment to atomic value/
y := &x
*y = atomic.AddUint64(y, 1) // MATCH /direct assignment to atomic value/
var su struct{ Counter uint64 }
su.Counter = atomic.AddUint64(&su.Counter, 1) // MATCH /direct assignment to atomic value/
z1 := atomic.AddUint64(&su.Counter, 1)
_ = z1 // Avoid err "z declared and not used"
var sp struct{ Counter *uint64 }
*sp.Counter = atomic.AddUint64(sp.Counter, 1) // MATCH /direct assignment to atomic value/
z2 := atomic.AddUint64(sp.Counter, 1)
_ = z2 // Avoid err "z declared and not used"
au := []uint64{10, 20}
au[0] = atomic.AddUint64(&au[0], 1) // MATCH /direct assignment to atomic value/
au[1] = atomic.AddUint64(&au[0], 1)
ap := []*uint64{&au[0], &au[1]}
*ap[0] = atomic.AddUint64(ap[0], 1) // MATCH /direct assignment to atomic value/
*ap[1] = atomic.AddUint64(ap[0], 1)
}
type T struct{}
func (T) AddUint64(addr *uint64, delta uint64) uint64 { return 0 }
func NonAtomic() {
x := uint64(1)
var atomic T
x = atomic.AddUint64(&x, 1) // MATCH /direct assignment to atomic value/
}

59
fixtures/empty-lines.go Normal file
View File

@ -0,0 +1,59 @@
// Test of empty-lines.
package fixtures
func f1(x *int) bool { // MATCH /extra empty line at the start of a block/
return x > 2
}
func f2(x *int) bool {
return x > 2 // MATCH /extra empty line at the end of a block/
}
func f3(x *int) bool { // MATCH /extra empty line at the start of a block/
return x > 2 // MATCH /extra empty line at the end of a block/
}
func f4(x *int) bool {
// This is fine.
return x > 2
}
func f5(x *int) bool { // MATCH /extra empty line at the start of a block/
// This is _not_ fine.
return x > 2
}
func f6(x *int) bool {
return x > 2
// This is fine.
}
func f7(x *int) bool {
return x > 2 // MATCH /extra empty line at the end of a block/
// This is _not_ fine.
}
func f8(*int) bool {
if x > 2 { // MATCH /extra empty line at the start of a block/
return true
}
return false
}
func f9(*int) bool {
if x > 2 {
return true // MATCH /extra empty line at the end of a block/
}
return false
}

View File

@ -0,0 +1,37 @@
package fixtures
import "fmt"
func foo() {
mySlice := []string{"A", "B", "C"}
for index, value := range mySlice {
go func() {
fmt.Printf("Index: %d\n", index) // MATCH /loop variable index captured by func literal/
fmt.Printf("Value: %s\n", value) // MATCH /loop variable value captured by func literal/
}()
}
myDict := make(map[string]int)
myDict["A"] = 1
myDict["B"] = 2
myDict["C"] = 3
for key, value := range myDict {
defer func() {
fmt.Printf("Index: %d\n", key) // MATCH /loop variable key captured by func literal/
fmt.Printf("Value: %s\n", value) // MATCH /loop variable value captured by func literal/
}()
}
for i, newg := range groups {
go func(newg int) {
newg.run(m.opts.Context,i) // MATCH /loop variable i captured by func literal/
}(newg)
}
for i, newg := range groups {
newg := newg
go func() {
newg.run(m.opts.Context,i) // MATCH /loop variable i captured by func literal/
}()
}
}

View File

@ -0,0 +1,21 @@
package fixtures
import (
"sync"
)
func foo(a int, b float32, c char, d sync.WaitGroup) { // MATCH /sync.WaitGroup passed by value, the function will get a copy of the original one/
}
func bar(a, b sync.WaitGroup) { // MATCH /sync.WaitGroup passed by value, the function will get a copy of the original one/
}
func baz(zz sync.WaitGroup) { // MATCH /sync.WaitGroup passed by value, the function will get a copy of the original one/
}
func ok(zz *sync.WaitGroup) {
}

View File

@ -18,7 +18,7 @@ func (f *Default) Name() string {
} }
// Format formats the failures gotten from the lint. // Format formats the failures gotten from the lint.
func (f *Default) Format(failures <-chan lint.Failure, config lint.RulesConfig) (string, error) { func (f *Default) Format(failures <-chan lint.Failure, _ lint.RulesConfig) (string, error) {
for failure := range failures { for failure := range failures {
fmt.Printf("%v: %s\n", failure.Position.Start, failure.Failure) fmt.Printf("%v: %s\n", failure.Position.Start, failure.Failure)
} }

View File

@ -19,7 +19,7 @@ func (f *Unix) Name() string {
} }
// Format formats the failures gotten from the lint. // Format formats the failures gotten from the lint.
func (f *Unix) Format(failures <-chan lint.Failure, config lint.RulesConfig) (string, error) { func (f *Unix) Format(failures <-chan lint.Failure, _ lint.RulesConfig) (string, error) {
for failure := range failures { for failure := range failures {
fmt.Printf("%v: [%s] %s\n", failure.Position.Start, failure.RuleName, failure.Failure) fmt.Printf("%v: [%s] %s\n", failure.Position.Start, failure.RuleName, failure.Failure)
} }

View File

@ -56,6 +56,11 @@ func (f *File) Render(x interface{}) string {
return buf.String() return buf.String()
} }
// CommentMap builds a comment map for the file.
func (f *File) CommentMap() ast.CommentMap {
return ast.NewCommentMap(f.Pkg.fset, f.AST, f.AST.Comments)
}
var basicTypeKinds = map[types.BasicKind]string{ var basicTypeKinds = map[types.BasicKind]string{
types.UntypedBool: "bool", types.UntypedBool: "bool",
types.UntypedInt: "int", types.UntypedInt: "int",

View File

@ -77,7 +77,9 @@ func (p *Package) TypeCheck() error {
anyFile = f anyFile = f
astFiles = append(astFiles, f.AST) astFiles = append(astFiles, f.AST)
} }
typesPkg, err := config.Check(anyFile.AST.Name.Name, p.fset, astFiles, info)
typesPkg, err := check(config, anyFile.AST.Name.Name, p.fset, astFiles, info)
// Remember the typechecking info, even if config.Check failed, // Remember the typechecking info, even if config.Check failed,
// since we will get partial information. // since we will get partial information.
p.TypesPkg = typesPkg p.TypesPkg = typesPkg
@ -86,6 +88,20 @@ func (p *Package) TypeCheck() error {
return err return err
} }
// check function encapsulates the call to go/types.Config.Check method and
// recovers if the called method panics (see issue #59)
func check(config *types.Config, n string, fset *token.FileSet, astFiles []*ast.File, info *types.Info) (p *types.Package, err error) {
defer func() {
if r := recover(); r != nil {
err, _ = r.(error)
p = nil
return
}
}()
return config.Check(n, fset, astFiles, info)
}
// TypeOf returns the type of an expression. // TypeOf returns the type of an expression.
func (p *Package) TypeOf(expr ast.Expr) types.Type { func (p *Package) TypeOf(expr ast.Expr) types.Type {
if p.TypesInfo == nil { if p.TypesInfo == nil {

94
rule/atomic.go Normal file
View File

@ -0,0 +1,94 @@
package rule
import (
"go/ast"
"go/token"
"go/types"
"github.com/mgechev/revive/lint"
)
// AtomicRule lints given else constructs.
type AtomicRule struct{}
// Apply applies the rule to given file.
func (r *AtomicRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
walker := atomic{
pkgTypesInfo: file.Pkg.TypesInfo,
onFailure: func(failure lint.Failure) {
failures = append(failures, failure)
},
}
ast.Walk(walker, file.AST)
return failures
}
// Name returns the rule name.
func (r *AtomicRule) Name() string {
return "atomic"
}
type atomic struct {
pkgTypesInfo *types.Info
onFailure func(lint.Failure)
}
func (w atomic) Visit(node ast.Node) ast.Visitor {
n, ok := node.(*ast.AssignStmt)
if !ok {
return w
}
if len(n.Lhs) != len(n.Rhs) {
return nil // skip assignment sub-tree
}
if len(n.Lhs) == 1 && n.Tok == token.DEFINE {
return nil // skip assignment sub-tree
}
for i, right := range n.Rhs {
call, ok := right.(*ast.CallExpr)
if !ok {
continue
}
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
continue
}
pkgIdent, _ := sel.X.(*ast.Ident)
if w.pkgTypesInfo != nil {
pkgName, ok := w.pkgTypesInfo.Uses[pkgIdent].(*types.PkgName)
if !ok || pkgName.Imported().Path() != "sync/atomic" {
continue
}
}
switch sel.Sel.Name {
case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr":
left := n.Lhs[i]
if len(call.Args) != 2 {
continue
}
arg := call.Args[0]
broken := false
if uarg, ok := arg.(*ast.UnaryExpr); ok && uarg.Op == token.AND {
broken = gofmt(left) == gofmt(uarg.X)
} else if star, ok := left.(*ast.StarExpr); ok {
broken = gofmt(star.X) == gofmt(arg)
}
if broken {
w.onFailure(lint.Failure{
Confidence: 1,
Failure: "direct assignment to atomic value",
Node: n,
})
}
}
}
return w
}

View File

@ -10,7 +10,7 @@ import (
type BlankImportsRule struct{} type BlankImportsRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *BlankImportsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *BlankImportsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
fileAst := file.AST fileAst := file.AST
@ -38,7 +38,7 @@ type lintBlankImports struct {
onFailure func(lint.Failure) onFailure func(lint.Failure)
} }
func (w lintBlankImports) Visit(n ast.Node) ast.Visitor { func (w lintBlankImports) Visit(_ ast.Node) ast.Visitor {
// In package main and in tests, we don't complain about blank imports. // In package main and in tests, we don't complain about blank imports.
if w.file.Pkg.IsMain() || w.file.IsTest() { if w.file.Pkg.IsMain() || w.file.IsTest() {
return nil return nil

View File

@ -11,7 +11,7 @@ import (
type BoolLiteralRule struct{} type BoolLiteralRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *BoolLiteralRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *BoolLiteralRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
onFailure := func(failure lint.Failure) { onFailure := func(failure lint.Failure) {

View File

@ -49,7 +49,7 @@ var allPkgs = packages{pkgs: make([]pkgMethods, 1)}
type ConfusingNamingRule struct{} type ConfusingNamingRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *ConfusingNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *ConfusingNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
fileAst := file.AST fileAst := file.AST
pkgm := allPkgs.methodNames(file.Pkg) pkgm := allPkgs.methodNames(file.Pkg)

View File

@ -10,7 +10,7 @@ import (
type ConfusingResultsRule struct{} type ConfusingResultsRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *ConfusingResultsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *ConfusingResultsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
fileAst := file.AST fileAst := file.AST
@ -59,9 +59,9 @@ func (w lintConfusingResults) Visit(n ast.Node) ast.Visitor {
Failure: "unnamed results of the same type may be confusing, consider using named results", Failure: "unnamed results of the same type may be confusing, consider using named results",
}) })
break break
} else {
lastType = t.Name
} }
lastType = t.Name
} }
return w return w

View File

@ -1,11 +1,8 @@
package rule package rule
import ( import (
"bytes"
"fmt"
"github.com/mgechev/revive/lint" "github.com/mgechev/revive/lint"
"go/ast" "go/ast"
"go/format"
"go/token" "go/token"
) )
@ -13,7 +10,7 @@ import (
type ConstantLogicalExprRule struct{} type ConstantLogicalExprRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *ConstantLogicalExprRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *ConstantLogicalExprRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
onFailure := func(failure lint.Failure) { onFailure := func(failure lint.Failure) {
@ -43,7 +40,7 @@ func (w *lintConstantLogicalExpr) Visit(node ast.Node) ast.Visitor {
return w return w
} }
if !w.areEqual(n.X, n.Y) { if gofmt(n.X) != gofmt(n.Y) { // check if subexpressions are the same
return w return w
} }
@ -81,21 +78,6 @@ func (w *lintConstantLogicalExpr) isInequalityOperator(t token.Token) bool {
return false return false
} }
func (w lintConstantLogicalExpr) areEqual(x, y ast.Expr) bool {
fset := token.NewFileSet()
var buf1 bytes.Buffer
if err := format.Node(&buf1, fset, x); err != nil {
return false // keep going in case of error
}
var buf2 bytes.Buffer
if err := format.Node(&buf2, fset, y); err != nil {
return false // keep going in case of error
}
return fmt.Sprintf("%s", buf1.Bytes()) == fmt.Sprintf("%s", buf2.Bytes())
}
func (w lintConstantLogicalExpr) newFailure(node ast.Node, msg string) { func (w lintConstantLogicalExpr) newFailure(node ast.Node, msg string) {
w.onFailure(lint.Failure{ w.onFailure(lint.Failure{
Confidence: 1, Confidence: 1,

View File

@ -10,7 +10,7 @@ import (
type ContextAsArgumentRule struct{} type ContextAsArgumentRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *ContextAsArgumentRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *ContextAsArgumentRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
fileAst := file.AST fileAst := file.AST

View File

@ -12,7 +12,7 @@ import (
type ContextKeysType struct{} type ContextKeysType struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *ContextKeysType) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *ContextKeysType) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
fileAst := file.AST fileAst := file.AST

View File

@ -47,7 +47,7 @@ type lintCyclomatic struct {
onFailure func(lint.Failure) onFailure func(lint.Failure)
} }
func (w lintCyclomatic) Visit(n ast.Node) ast.Visitor { func (w lintCyclomatic) Visit(_ ast.Node) ast.Visitor {
f := w.file f := w.file
for _, decl := range f.AST.Decls { for _, decl := range f.AST.Decls {
if fn, ok := decl.(*ast.FuncDecl); ok { if fn, ok := decl.(*ast.FuncDecl); ok {

View File

@ -11,7 +11,7 @@ import (
type DeepExitRule struct{} type DeepExitRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *DeepExitRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *DeepExitRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
onFailure := func(failure lint.Failure) { onFailure := func(failure lint.Failure) {
failures = append(failures, failure) failures = append(failures, failure)

View File

@ -10,7 +10,7 @@ import (
type DotImportsRule struct{} type DotImportsRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *DotImportsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *DotImportsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
fileAst := file.AST fileAst := file.AST
@ -38,7 +38,7 @@ type lintImports struct {
onFailure func(lint.Failure) onFailure func(lint.Failure)
} }
func (w lintImports) Visit(n ast.Node) ast.Visitor { func (w lintImports) Visit(_ ast.Node) ast.Visitor {
for i, is := range w.fileAst.Imports { for i, is := range w.fileAst.Imports {
_ = i _ = i
if is.Name != nil && is.Name.Name == "." && !w.file.IsTest() { if is.Name != nil && is.Name.Name == "." && !w.file.IsTest() {

View File

@ -10,7 +10,7 @@ import (
type EmptyBlockRule struct{} type EmptyBlockRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *EmptyBlockRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *EmptyBlockRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
onFailure := func(failure lint.Failure) { onFailure := func(failure lint.Failure) {

113
rule/empty-lines.go Normal file
View File

@ -0,0 +1,113 @@
package rule
import (
"go/ast"
"go/token"
"github.com/mgechev/revive/lint"
)
// EmptyLinesRule lints empty lines in blocks.
type EmptyLinesRule struct{}
// Apply applies the rule to given file.
func (r *EmptyLinesRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
}
w := lintEmptyLines{file, file.CommentMap(), onFailure}
ast.Walk(w, file.AST)
return failures
}
// Name returns the rule name.
func (r *EmptyLinesRule) Name() string {
return "empty-lines"
}
type lintEmptyLines struct {
file *lint.File
cmap ast.CommentMap
onFailure func(lint.Failure)
}
func (w lintEmptyLines) Visit(node ast.Node) ast.Visitor {
block, ok := node.(*ast.BlockStmt)
if !ok {
return w
}
w.checkStart(block)
w.checkEnd(block)
return w
}
func (w lintEmptyLines) checkStart(block *ast.BlockStmt) {
if len(block.List) == 0 {
return
}
start := w.position(block.Lbrace)
firstNode := block.List[0]
if w.commentBetween(start, firstNode) {
return
}
first := w.position(firstNode.Pos())
if first.Line-start.Line > 1 {
w.onFailure(lint.Failure{
Confidence: 1,
Node: block,
Category: "style",
URL: "#empty-lines",
Failure: "extra empty line at the start of a block",
})
}
}
func (w lintEmptyLines) checkEnd(block *ast.BlockStmt) {
if len(block.List) < 1 {
return
}
end := w.position(block.Rbrace)
lastNode := block.List[len(block.List)-1]
if w.commentBetween(end, lastNode) {
return
}
last := w.position(lastNode.Pos())
if end.Line-last.Line > 1 {
w.onFailure(lint.Failure{
Confidence: 1,
Node: lastNode,
Category: "style",
URL: "#empty-lines",
Failure: "extra empty line at the end of a block",
})
}
}
func (w lintEmptyLines) commentBetween(position token.Position, node ast.Node) bool {
comments := w.cmap.Filter(node).Comments()
if len(comments) == 0 {
return false
}
commentStart := w.position(comments[0].Pos())
if commentStart.Line-position.Line == 1 || commentStart.Line-position.Line == -1 {
return true
}
return false
}
func (w lintEmptyLines) position(pos token.Pos) token.Position {
return w.file.ToPosition(pos)
}

View File

@ -13,7 +13,7 @@ import (
type ErrorNamingRule struct{} type ErrorNamingRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *ErrorNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *ErrorNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
fileAst := file.AST fileAst := file.AST
@ -41,7 +41,7 @@ type lintErrors struct {
onFailure func(lint.Failure) onFailure func(lint.Failure)
} }
func (w lintErrors) Visit(n ast.Node) ast.Visitor { func (w lintErrors) Visit(_ ast.Node) ast.Visitor {
for _, decl := range w.fileAst.Decls { for _, decl := range w.fileAst.Decls {
gd, ok := decl.(*ast.GenDecl) gd, ok := decl.(*ast.GenDecl)
if !ok || gd.Tok != token.VAR { if !ok || gd.Tok != token.VAR {

View File

@ -10,7 +10,7 @@ import (
type ErrorReturnRule struct{} type ErrorReturnRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *ErrorReturnRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *ErrorReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
fileAst := file.AST fileAst := file.AST

View File

@ -14,7 +14,7 @@ import (
type ErrorStringsRule struct{} type ErrorStringsRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *ErrorStringsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *ErrorStringsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
fileAst := file.AST fileAst := file.AST

View File

@ -13,7 +13,7 @@ import (
type ErrorfRule struct{} type ErrorfRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *ErrorfRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *ErrorfRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
fileAst := file.AST fileAst := file.AST

View File

@ -15,7 +15,7 @@ import (
type ExportedRule struct{} type ExportedRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *ExportedRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *ExportedRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
if isTest(file) { if isTest(file) {

View File

@ -51,7 +51,7 @@ type lintFileHeader struct {
onFailure func(lint.Failure) onFailure func(lint.Failure)
} }
func (w lintFileHeader) Visit(n ast.Node) ast.Visitor { func (w lintFileHeader) Visit(_ ast.Node) ast.Visitor {
g := w.fileAst.Comments[0] g := w.fileAst.Comments[0]
failure := lint.Failure{ failure := lint.Failure{
Node: w.fileAst, Node: w.fileAst,

View File

@ -10,7 +10,7 @@ import (
type FlagParamRule struct{} type FlagParamRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *FlagParamRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *FlagParamRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
onFailure := func(failure lint.Failure) { onFailure := func(failure lint.Failure) {

View File

@ -12,7 +12,7 @@ import (
type GetReturnRule struct{} type GetReturnRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *GetReturnRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *GetReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
onFailure := func(failure lint.Failure) { onFailure := func(failure lint.Failure) {

View File

@ -12,7 +12,7 @@ import (
type IfReturnRule struct{} type IfReturnRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *IfReturnRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *IfReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
onFailure := func(failure lint.Failure) { onFailure := func(failure lint.Failure) {

View File

@ -54,7 +54,7 @@ type blacklistedImports struct {
blacklist map[string]bool blacklist map[string]bool
} }
func (w blacklistedImports) Visit(n ast.Node) ast.Visitor { func (w blacklistedImports) Visit(_ ast.Node) ast.Visitor {
for _, is := range w.fileAst.Imports { for _, is := range w.fileAst.Imports {
if is.Path != nil && !w.file.IsTest() && w.blacklist[is.Path.Value] { if is.Path != nil && !w.file.IsTest() && w.blacklist[is.Path.Value] {
w.onFailure(lint.Failure{ w.onFailure(lint.Failure{

View File

@ -12,7 +12,7 @@ import (
type IncrementDecrementRule struct{} type IncrementDecrementRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *IncrementDecrementRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *IncrementDecrementRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
fileAst := file.AST fileAst := file.AST

View File

@ -11,7 +11,7 @@ import (
type IndentErrorFlowRule struct{} type IndentErrorFlowRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *IndentErrorFlowRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *IndentErrorFlowRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
onFailure := func(failure lint.Failure) { onFailure := func(failure lint.Failure) {
@ -77,4 +77,3 @@ func (w lintElse) Visit(node ast.Node) ast.Visitor {
} }
return w return w
} }

View File

@ -11,7 +11,7 @@ import (
type ModifiesParamRule struct{} type ModifiesParamRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *ModifiesParamRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *ModifiesParamRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
onFailure := func(failure lint.Failure) { onFailure := func(failure lint.Failure) {

View File

@ -124,7 +124,7 @@ func (w lintModifiesValRecRule) skipType(t ast.Expr) bool {
return strings.HasPrefix(rtName, "[]") || strings.HasPrefix(rtName, "map[") return strings.HasPrefix(rtName, "[]") || strings.HasPrefix(rtName, "map[")
} }
func (_ lintModifiesValRecRule) getNameFromExpr(ie ast.Expr) string { func (lintModifiesValRecRule) getNameFromExpr(ie ast.Expr) string {
ident, ok := ie.(*ast.Ident) ident, ok := ie.(*ast.Ident)
if !ok { if !ok {
return "" return ""

View File

@ -17,7 +17,7 @@ import (
type PackageCommentsRule struct{} type PackageCommentsRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *PackageCommentsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *PackageCommentsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
if isTest(file) { if isTest(file) {
@ -45,7 +45,7 @@ type lintPackageComments struct {
onFailure func(lint.Failure) onFailure func(lint.Failure)
} }
func (l *lintPackageComments) Visit(n ast.Node) ast.Visitor { func (l *lintPackageComments) Visit(_ ast.Node) ast.Visitor {
if l.file.IsTest() { if l.file.IsTest() {
return nil return nil
} }

View File

@ -0,0 +1,111 @@
package rule
import (
"fmt"
"go/ast"
"github.com/mgechev/revive/lint"
)
// RangeValInClosureRule lints given else constructs.
type RangeValInClosureRule struct{}
// Apply applies the rule to given file.
func (r *RangeValInClosureRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
walker := rangeValInClosure{
onFailure: func(failure lint.Failure) {
failures = append(failures, failure)
},
}
ast.Walk(walker, file.AST)
return failures
}
// Name returns the rule name.
func (r *RangeValInClosureRule) Name() string {
return "range-val-in-closure"
}
type rangeValInClosure struct {
onFailure func(lint.Failure)
}
func (w rangeValInClosure) Visit(node ast.Node) ast.Visitor {
// Find the variables updated by the loop statement.
var vars []*ast.Ident
addVar := func(expr ast.Expr) {
if id, ok := expr.(*ast.Ident); ok {
vars = append(vars, id)
}
}
var body *ast.BlockStmt
switch n := node.(type) {
case *ast.RangeStmt:
body = n.Body
addVar(n.Key)
addVar(n.Value)
case *ast.ForStmt:
body = n.Body
switch post := n.Post.(type) {
case *ast.AssignStmt:
// e.g. for p = head; p != nil; p = p.next
for _, lhs := range post.Lhs {
addVar(lhs)
}
case *ast.IncDecStmt:
// e.g. for i := 0; i < n; i++
addVar(post.X)
}
}
if vars == nil {
return w
}
// Inspect a go or defer statement
// if it's the last one in the loop body.
// (We give up if there are following statements,
// because it's hard to prove go isn't followed by wait,
// or defer by return.)
if len(body.List) == 0 {
return w
}
var last *ast.CallExpr
switch s := body.List[len(body.List)-1].(type) {
case *ast.GoStmt:
last = s.Call
case *ast.DeferStmt:
last = s.Call
default:
return w
}
lit, ok := last.Fun.(*ast.FuncLit)
if !ok {
return w
}
if lit.Type == nil {
// Not referring to a variable (e.g. struct field name)
return w
}
ast.Inspect(lit.Body, func(n ast.Node) bool {
id, ok := n.(*ast.Ident)
if !ok || id.Obj == nil {
return true
}
for _, v := range vars {
if v.Obj == id.Obj {
w.onFailure(lint.Failure{
Confidence: 1,
Failure: fmt.Sprintf("loop variable %v captured by func literal", id.Name),
Node: n,
})
}
}
return true
})
return w
}

View File

@ -12,7 +12,7 @@ import (
type RangeRule struct{} type RangeRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *RangeRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *RangeRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
onFailure := func(failure lint.Failure) { onFailure := func(failure lint.Failure) {

View File

@ -11,7 +11,7 @@ import (
type ReceiverNamingRule struct{} type ReceiverNamingRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *ReceiverNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *ReceiverNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
fileAst := file.AST fileAst := file.AST

View File

@ -11,7 +11,7 @@ import (
type RedefinesBuiltinIDRule struct{} type RedefinesBuiltinIDRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *RedefinesBuiltinIDRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *RedefinesBuiltinIDRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
var builtInConstAndVars = map[string]bool{ var builtInConstAndVars = map[string]bool{

View File

@ -12,7 +12,7 @@ import (
type SuperfluousElseRule struct{} type SuperfluousElseRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *SuperfluousElseRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *SuperfluousElseRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
onFailure := func(failure lint.Failure) { onFailure := func(failure lint.Failure) {
failures = append(failures, failure) failures = append(failures, failure)

View File

@ -13,7 +13,7 @@ import (
type TimeNamingRule struct{} type TimeNamingRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *TimeNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *TimeNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
onFailure := func(failure lint.Failure) { onFailure := func(failure lint.Failure) {
@ -50,7 +50,7 @@ func (w *lintTimeNames) Visit(node ast.Node) ast.Visitor {
if pt, ok := typ.(*types.Pointer); ok { if pt, ok := typ.(*types.Pointer); ok {
typ = pt.Elem() typ = pt.Elem()
} }
if !isNamedType(w.file.Pkg, typ, "time", "Duration") { if !isNamedType(typ, "time", "Duration") {
continue continue
} }
suffix := "" suffix := ""
@ -83,7 +83,7 @@ var timeSuffixes = []string{
"MS", "Ms", "MS", "Ms",
} }
func isNamedType(p *lint.Package, typ types.Type, importPath, name string) bool { func isNamedType(typ types.Type, importPath, name string) bool {
n, ok := typ.(*types.Named) n, ok := typ.(*types.Named)
if !ok { if !ok {
return false return false

View File

@ -12,7 +12,7 @@ import (
type UnexportedReturnRule struct{} type UnexportedReturnRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *UnexportedReturnRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *UnexportedReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
fileAst := file.AST fileAst := file.AST

View File

@ -11,7 +11,7 @@ import (
type UnnecessaryStmtRule struct{} type UnnecessaryStmtRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *UnnecessaryStmtRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *UnnecessaryStmtRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
onFailure := func(failure lint.Failure) { onFailure := func(failure lint.Failure) {
failures = append(failures, failure) failures = append(failures, failure)

View File

@ -10,7 +10,7 @@ import (
type UnreachableCodeRule struct{} type UnreachableCodeRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *UnreachableCodeRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *UnreachableCodeRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
onFailure := func(failure lint.Failure) { onFailure := func(failure lint.Failure) {
failures = append(failures, failure) failures = append(failures, failure)

View File

@ -1,8 +1,10 @@
package rule package rule
import ( import (
"bytes"
"fmt" "fmt"
"go/ast" "go/ast"
"go/printer"
"go/token" "go/token"
"go/types" "go/types"
"regexp" "regexp"
@ -179,3 +181,11 @@ func isExprABooleanLit(n ast.Node) (lexeme string, ok bool) {
return oper.Name, (oper.Name == trueName || oper.Name == falseName) return oper.Name, (oper.Name == trueName || oper.Name == falseName)
} }
// gofmt returns a string representation of the expression.
func gofmt(x ast.Expr) string {
buf := bytes.Buffer{}
fs := token.NewFileSet()
printer.Fprint(&buf, fs, x)
return buf.String()
}

View File

@ -13,7 +13,7 @@ import (
type VarDeclarationsRule struct{} type VarDeclarationsRule struct{}
// Apply applies the rule to given file. // Apply applies the rule to given file.
func (r *VarDeclarationsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { func (r *VarDeclarationsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure var failures []lint.Failure
fileAst := file.AST fileAst := file.AST

View File

@ -0,0 +1,66 @@
package rule
import (
"go/ast"
"github.com/mgechev/revive/lint"
)
// WaitGroupByValueRule lints sync.WaitGroup passed by copy in functions.
type WaitGroupByValueRule struct{}
// Apply applies the rule to given file.
func (r *WaitGroupByValueRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
}
w := lintWaitGroupByValueRule{onFailure: onFailure}
ast.Walk(w, file.AST)
return failures
}
// Name returns the rule name.
func (r *WaitGroupByValueRule) Name() string {
return "waitgroup-by-value"
}
type lintWaitGroupByValueRule struct {
onFailure func(lint.Failure)
}
func (w lintWaitGroupByValueRule) Visit(node ast.Node) ast.Visitor {
// look for function declarations
fd, ok := node.(*ast.FuncDecl)
if !ok {
return w
}
// Check all function's parameters
for _, field := range fd.Type.Params.List {
if !w.isWaitGroup(field.Type) {
continue
}
w.onFailure(lint.Failure{
Confidence: 1,
Node: field,
Failure: "sync.WaitGroup passed by value, the function will get a copy of the original one",
})
}
return nil
}
func (lintWaitGroupByValueRule) isWaitGroup(ft ast.Expr) bool {
se, ok := ft.(*ast.SelectorExpr)
if !ok {
return false
}
x, _ := se.X.(*ast.Ident)
sel := se.Sel.Name
return x.Name == "sync" && sel == "WaitGroup"
}

12
test/atomic_test.go Normal file
View File

@ -0,0 +1,12 @@
package test
import (
"testing"
"github.com/mgechev/revive/rule"
)
// Atomic rule.
func TestAtomic(t *testing.T) {
testRule(t, "atomic", &rule.AtomicRule{})
}

12
test/empty-lines_test.go Normal file
View File

@ -0,0 +1,12 @@
package test
import (
"testing"
"github.com/mgechev/revive/rule"
)
// TestEmptyLines rule.
func TestEmptyLines(t *testing.T) {
testRule(t, "empty-lines", &rule.EmptyLinesRule{})
}

View File

@ -0,0 +1,12 @@
package test
import (
"testing"
"github.com/mgechev/revive/lint"
"github.com/mgechev/revive/rule"
)
func TestRangeValInClosure(t *testing.T) {
testRule(t, "range-val-in-closure", &rule.RangeValInClosureRule{}, &lint.RuleConfig{})
}

View File

@ -35,13 +35,13 @@ func testRule(t *testing.T, filename string, rule lint.Rule, config ...*lint.Rul
c[rule.Name()] = *config[0] c[rule.Name()] = *config[0]
} }
if parseInstructions(t, filename, src) == nil { if parseInstructions(t, filename, src) == nil {
assertSuccess(t, baseDir, stat, src, []lint.Rule{rule}, c) assertSuccess(t, baseDir, stat, []lint.Rule{rule}, c)
return return
} }
assertFailures(t, baseDir, stat, src, []lint.Rule{rule}, c) assertFailures(t, baseDir, stat, src, []lint.Rule{rule}, c)
} }
func assertSuccess(t *testing.T, baseDir string, fi os.FileInfo, src []byte, rules []lint.Rule, config map[string]lint.RuleConfig) error { func assertSuccess(t *testing.T, baseDir string, fi os.FileInfo, rules []lint.Rule, config map[string]lint.RuleConfig) error {
l := lint.New(func(file string) ([]byte, error) { l := lint.New(func(file string) ([]byte, error) {
return ioutil.ReadFile(baseDir + file) return ioutil.ReadFile(baseDir + file)
}) })
@ -220,7 +220,8 @@ func srcLine(src []byte, p token.Position) string {
return string(src[lo:hi]) return string(src[lo:hi])
} }
func TestLine(t *testing.T) { // TestLine tests srcLine function
func TestLine(t *testing.T) { //revive:disable-line:exported
tests := []struct { tests := []struct {
src string src string
offset int offset int
@ -242,7 +243,8 @@ func TestLine(t *testing.T) {
} }
} }
func TestLintName(t *testing.T) { // TestLintName tests lint.Name function
func TestLintName(t *testing.T) { //revive:disable-line:exported
tests := []struct { tests := []struct {
name, want string name, want string
}{ }{
@ -301,7 +303,8 @@ func exportedType(typ types.Type) bool {
return true return true
} }
func TestExportedType(t *testing.T) { // TestExportedType tests exportedType function
func TestExportedType(t *testing.T) { //revive:disable-line:exported
tests := []struct { tests := []struct {
typString string typString string
exp bool exp bool
@ -356,7 +359,8 @@ func isGenerated(src []byte) bool {
return false return false
} }
func TestIsGenerated(t *testing.T) { // TestIsGenerated tests isGenerated function
func TestIsGenerated(t *testing.T) { //revive:disable-line:exported
tests := []struct { tests := []struct {
source string source string
generated bool generated bool

View File

@ -0,0 +1,11 @@
package test
import (
"testing"
"github.com/mgechev/revive/rule"
)
func TestWaitGroupByValue(t *testing.T) {
testRule(t, "waitgroup-by-value", &rule.WaitGroupByValueRule{})
}

View File

@ -13,3 +13,6 @@
[rule.receiver-naming] [rule.receiver-naming]
[rule.indent-error-flow] [rule.indent-error-flow]
[rule.empty-block] [rule.empty-block]
[rule.range-val-in-closure]
[rule.waitgroup-by-value]
[rule.atomic]