mirror of
https://github.com/mgechev/revive.git
synced 2025-06-08 23:26:29 +02:00
fix(receiver-naming): distinguish types with parameters (#692)
* fix(receiver-naming): distinguish types with parameters * chore: run tests using supported Go versions matrix
This commit is contained in:
parent
76ef1d75d1
commit
dc30eb1182
.github/workflows
internal/typeparams
lint
rule
test
testdata
@ -1,5 +1,6 @@
|
|||||||
name: Build and Test
|
name: Build
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, edited, synchronize, reopened]
|
types: [opened, edited, synchronize, reopened]
|
||||||
|
|
||||||
@ -11,10 +12,3 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: build
|
- name: build
|
||||||
run: make build
|
run: make build
|
||||||
test:
|
|
||||||
name: Test
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: test
|
|
||||||
run: make test
|
|
1
.github/workflows/lint.yaml
vendored
1
.github/workflows/lint.yaml
vendored
@ -1,5 +1,6 @@
|
|||||||
name: Lint
|
name: Lint
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, edited, synchronize, reopened]
|
types: [opened, edited, synchronize, reopened]
|
||||||
|
|
||||||
|
28
.github/workflows/test.yaml
vendored
Normal file
28
.github/workflows/test.yaml
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
name: Test
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, edited, synchronize, reopened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
go-version:
|
||||||
|
- 1.16.x
|
||||||
|
- 1.17.x
|
||||||
|
- 1.18.x
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3.0.2
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v3.2.0
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go-version }}
|
||||||
|
cache: true
|
||||||
|
cache-dependency-path: '**/go.sum'
|
||||||
|
- name: Run tests
|
||||||
|
run: go test -race ./...
|
29
internal/typeparams/typeparams.go
Normal file
29
internal/typeparams/typeparams.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Package typeparams provides utilities for working with Go ASTs with support
|
||||||
|
// for type parameters when built with Go 1.18 and higher.
|
||||||
|
package typeparams
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enabled reports whether type parameters are enabled in the current build
|
||||||
|
// environment.
|
||||||
|
func Enabled() bool {
|
||||||
|
return enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReceiverType returns the named type of the method receiver, sans "*" and type
|
||||||
|
// parameters, or "invalid-type" if fn.Recv is ill formed.
|
||||||
|
func ReceiverType(fn *ast.FuncDecl) string {
|
||||||
|
e := fn.Recv.List[0].Type
|
||||||
|
if s, ok := e.(*ast.StarExpr); ok {
|
||||||
|
e = s.X
|
||||||
|
}
|
||||||
|
if enabled {
|
||||||
|
e = unpackIndexExpr(e)
|
||||||
|
}
|
||||||
|
if id, ok := e.(*ast.Ident); ok {
|
||||||
|
return id.Name
|
||||||
|
}
|
||||||
|
return "invalid-type"
|
||||||
|
}
|
12
internal/typeparams/typeparams_go117.go
Normal file
12
internal/typeparams/typeparams_go117.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
//go:build !go1.18
|
||||||
|
// +build !go1.18
|
||||||
|
|
||||||
|
package typeparams
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
const enabled = false
|
||||||
|
|
||||||
|
func unpackIndexExpr(e ast.Expr) ast.Expr { return e }
|
20
internal/typeparams/typeparams_go118.go
Normal file
20
internal/typeparams/typeparams_go118.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
//go:build go1.18
|
||||||
|
// +build go1.18
|
||||||
|
|
||||||
|
package typeparams
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
const enabled = true
|
||||||
|
|
||||||
|
func unpackIndexExpr(e ast.Expr) ast.Expr {
|
||||||
|
switch e := e.(type) {
|
||||||
|
case *ast.IndexExpr:
|
||||||
|
return e.X
|
||||||
|
case *ast.IndexListExpr:
|
||||||
|
return e.X
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
@ -7,6 +7,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"golang.org/x/tools/go/gcexportdata"
|
"golang.org/x/tools/go/gcexportdata"
|
||||||
|
|
||||||
|
"github.com/mgechev/revive/internal/typeparams"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Package represents a package in the project.
|
// Package represents a package in the project.
|
||||||
@ -146,7 +148,7 @@ func (w *walker) Visit(n ast.Node) ast.Visitor {
|
|||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
// TODO(dsymonds): We could check the signature to be more precise.
|
// TODO(dsymonds): We could check the signature to be more precise.
|
||||||
recv := receiverType(fn)
|
recv := typeparams.ReceiverType(fn)
|
||||||
if i, ok := w.nmap[fn.Name.Name]; ok {
|
if i, ok := w.nmap[fn.Name.Name]; ok {
|
||||||
w.has[recv] |= i
|
w.has[recv] |= i
|
||||||
}
|
}
|
||||||
@ -174,21 +176,6 @@ func (p *Package) scanSortable() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// receiverType returns the named type of the method receiver, sans "*",
|
|
||||||
// or "invalid-type" if fn.Recv is ill formed.
|
|
||||||
func receiverType(fn *ast.FuncDecl) string {
|
|
||||||
switch e := fn.Recv.List[0].Type.(type) {
|
|
||||||
case *ast.Ident:
|
|
||||||
return e.Name
|
|
||||||
case *ast.StarExpr:
|
|
||||||
if id, ok := e.X.(*ast.Ident); ok {
|
|
||||||
return id.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The parser accepts much more than just the legal forms.
|
|
||||||
return "invalid-type"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Package) lint(rules []Rule, config Config, failures chan Failure) {
|
func (p *Package) lint(rules []Rule, config Config, failures chan Failure) {
|
||||||
p.scanSortable()
|
p.scanSortable()
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/mgechev/revive/internal/typeparams"
|
||||||
"github.com/mgechev/revive/lint"
|
"github.com/mgechev/revive/lint"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -116,7 +117,7 @@ func (w *lintExported) lintFuncDoc(fn *ast.FuncDecl) {
|
|||||||
if fn.Recv != nil && len(fn.Recv.List) > 0 {
|
if fn.Recv != nil && len(fn.Recv.List) > 0 {
|
||||||
// method
|
// method
|
||||||
kind = "method"
|
kind = "method"
|
||||||
recv := receiverType(fn)
|
recv := typeparams.ReceiverType(fn)
|
||||||
if !w.checkPrivateReceivers && !ast.IsExported(recv) {
|
if !w.checkPrivateReceivers && !ast.IsExported(recv) {
|
||||||
// receiver is unexported
|
// receiver is unexported
|
||||||
return
|
return
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
|
|
||||||
|
"github.com/mgechev/revive/internal/typeparams"
|
||||||
"github.com/mgechev/revive/lint"
|
"github.com/mgechev/revive/lint"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ func (w lintReceiverName) Visit(n ast.Node) ast.Visitor {
|
|||||||
})
|
})
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
recv := receiverType(fn)
|
recv := typeparams.ReceiverType(fn)
|
||||||
if prev, ok := w.typeReceiver[recv]; ok && prev != name {
|
if prev, ok := w.typeReceiver[recv]; ok && prev != name {
|
||||||
w.onFailure(lint.Failure{
|
w.onFailure(lint.Failure{
|
||||||
Node: n,
|
Node: n,
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"go/ast"
|
"go/ast"
|
||||||
"go/types"
|
"go/types"
|
||||||
|
|
||||||
|
"github.com/mgechev/revive/internal/typeparams"
|
||||||
"github.com/mgechev/revive/lint"
|
"github.com/mgechev/revive/lint"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -55,7 +56,7 @@ func (w lintUnexportedReturn) Visit(n ast.Node) ast.Visitor {
|
|||||||
thing := "func"
|
thing := "func"
|
||||||
if fn.Recv != nil && len(fn.Recv.List) > 0 {
|
if fn.Recv != nil && len(fn.Recv.List) > 0 {
|
||||||
thing = "method"
|
thing = "method"
|
||||||
if !ast.IsExported(receiverType(fn)) {
|
if !ast.IsExported(typeparams.ReceiverType(fn)) {
|
||||||
// Don't report exported methods of unexported types,
|
// Don't report exported methods of unexported types,
|
||||||
// such as private implementations of sort.Interface.
|
// such as private implementations of sort.Interface.
|
||||||
return nil
|
return nil
|
||||||
|
@ -26,19 +26,6 @@ var commonMethods = map[string]bool{
|
|||||||
"Unwrap": true,
|
"Unwrap": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func receiverType(fn *ast.FuncDecl) string {
|
|
||||||
switch e := fn.Recv.List[0].Type.(type) {
|
|
||||||
case *ast.Ident:
|
|
||||||
return e.Name
|
|
||||||
case *ast.StarExpr:
|
|
||||||
if id, ok := e.X.(*ast.Ident); ok {
|
|
||||||
return id.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The parser accepts much more than just the legal forms.
|
|
||||||
return "invalid-type"
|
|
||||||
}
|
|
||||||
|
|
||||||
var knownNameExceptions = map[string]bool{
|
var knownNameExceptions = map[string]bool{
|
||||||
"LastInsertId": true, // must match database/sql
|
"LastInsertId": true, // must match database/sql
|
||||||
"kWh": true,
|
"kWh": true,
|
||||||
|
15
test/receiver-naming_test.go
Normal file
15
test/receiver-naming_test.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mgechev/revive/internal/typeparams"
|
||||||
|
"github.com/mgechev/revive/rule"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReceiverNamingTypeParams(t *testing.T) {
|
||||||
|
if !typeparams.Enabled() {
|
||||||
|
t.Skip("type parameters are not enabled in the current build environment")
|
||||||
|
}
|
||||||
|
testRule(t, "receiver-naming-issue-669", &rule.ReceiverNamingRule{})
|
||||||
|
}
|
37
testdata/receiver-naming-issue-669.go
vendored
Normal file
37
testdata/receiver-naming-issue-669.go
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package fixtures
|
||||||
|
|
||||||
|
type gen1[T any] struct{}
|
||||||
|
|
||||||
|
func (g gen1[T]) f1() {}
|
||||||
|
|
||||||
|
func (g gen1[U]) f2() {}
|
||||||
|
|
||||||
|
func (n gen1[T]) f3() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen1/
|
||||||
|
|
||||||
|
func (n gen1[U]) f4() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen1/
|
||||||
|
|
||||||
|
func (n gen1[V]) f5() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen1/
|
||||||
|
|
||||||
|
func (n *gen1[T]) f6() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen1/
|
||||||
|
|
||||||
|
func (n *gen1[U]) f7() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen1/
|
||||||
|
|
||||||
|
func (n *gen1[V]) f8() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen1/
|
||||||
|
|
||||||
|
type gen2[T1, T2 any] struct{}
|
||||||
|
|
||||||
|
func (g gen2[T1, T2]) f1() {}
|
||||||
|
|
||||||
|
func (g gen2[U1, U2]) f2() {}
|
||||||
|
|
||||||
|
func (n gen2[T1, T2]) f3() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen2/
|
||||||
|
|
||||||
|
func (n gen2[U1, U2]) f4() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen2/
|
||||||
|
|
||||||
|
func (n gen2[V1, V2]) f5() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen2/
|
||||||
|
|
||||||
|
func (n *gen2[T1, T2]) f6() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen2/
|
||||||
|
|
||||||
|
func (n *gen2[U1, U2]) f7() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen2/
|
||||||
|
|
||||||
|
func (n *gen2[V1, V2]) f8() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen2/
|
Loading…
x
Reference in New Issue
Block a user