Skip to content

Commit

Permalink
Go 1.23 (#46)
Browse files Browse the repository at this point in the history
* incredible refactoring based on linters writing experience

* more tests for nested errors

* Go 1.23

* upgrade deps
  • Loading branch information
Antonboom authored Oct 4, 2024
1 parent 80e65c6 commit 2ed8830
Show file tree
Hide file tree
Showing 21 changed files with 260 additions and 523 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ on:
branches: [ master ]

env:
GO_VERSION: ^1.22
GOLANGCI_LINT_VERSION: v1.57.2
GO_VERSION: ^1.23
GOLANGCI_LINT_VERSION: v1.61.0

permissions:
contents: read
Expand Down
5 changes: 2 additions & 3 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@ linters:
- asciicheck
- bidichk
- bodyclose
- copyloopvar
- dogsled
- dupl
- durationcheck
- errcheck
- errchkjson
- errname
- execinquery
- exhaustive
- exportloopref
- forbidigo
- gci
- gocheckcompilerdirectives
Expand All @@ -40,7 +39,6 @@ linters:
- gofumpt
- goheader
- goimports
- gomnd
- gomoddirectives
- gomodguard
- goprintffuncname
Expand All @@ -53,6 +51,7 @@ linters:
- makezero
- misspell
- mirror
- mnd
- nakedret
- nilerr
- nilnil
Expand Down
234 changes: 117 additions & 117 deletions README.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ tasks:
- task: tidy
- task: fmt
- task: lint
- task: tests
- task: test
- task: install

tools:install:
Expand All @@ -34,12 +34,12 @@ tasks:
- echo "Lint..."
- golangci-lint run --fix ./...

tests:
test:
cmds:
- echo "Tests..."
- go test ./...

tests:coverage:
test:coverage:
cmds:
- echo "Tests with coverage..."
- go test -coverprofile=coverage.out ./...
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module github.com/Antonboom/errname

go 1.20
go 1.22.1

require golang.org/x/tools v0.24.0
require golang.org/x/tools v0.26.0

require (
golang.org/x/mod v0.20.0 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/sync v0.8.0 // indirect
)
9 changes: 5 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
117 changes: 41 additions & 76 deletions pkg/analyzer/analyzer.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package analyzer

import (
"fmt"
"go/ast"
"go/token"
"strconv"
"strings"
"go/types"
"unicode"

"golang.org/x/tools/go/analysis"
Expand All @@ -23,81 +21,56 @@ func New() *analysis.Analyzer {
}
}

type stringSet = map[string]struct{}

var (
importNodes = []ast.Node{(*ast.ImportSpec)(nil)}
typeNodes = []ast.Node{(*ast.TypeSpec)(nil)}
funcNodes = []ast.Node{(*ast.FuncDecl)(nil)}
)

func run(pass *analysis.Pass) (interface{}, error) {
insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)

pkgAliases := map[string]string{}
insp.Preorder(importNodes, func(node ast.Node) {
i := node.(*ast.ImportSpec)
if n := i.Name; n != nil && i.Path != nil {
if path, err := strconv.Unquote(i.Path.Value); err == nil {
pkgAliases[n.Name] = getPkgFromPath(path)
}
}
})

allTypes := stringSet{}
typesSpecs := map[string]*ast.TypeSpec{}
insp.Preorder(typeNodes, func(node ast.Node) {
t := node.(*ast.TypeSpec)
allTypes[t.Name.Name] = struct{}{}
typesSpecs[t.Name.Name] = t
})

errorTypes := stringSet{}
insp.Preorder(funcNodes, func(node ast.Node) {
f := node.(*ast.FuncDecl)
t, ok := isMethodError(f)
if !ok {
return
}
errorTypes[t] = struct{}{}

tSpec, ok := typesSpecs[t]
if !ok {
panic(fmt.Sprintf("no specification for type %q", t))
}

if _, ok := tSpec.Type.(*ast.ArrayType); ok {
if !isValidErrorArrayTypeName(t) {
reportAboutErrorType(pass, tSpec.Pos(), t, true)
}
} else if !isValidErrorTypeName(t) {
reportAboutErrorType(pass, tSpec.Pos(), t, false)
insp.Nodes([]ast.Node{
(*ast.TypeSpec)(nil),
(*ast.ValueSpec)(nil),
(*ast.FuncDecl)(nil),
}, func(node ast.Node, push bool) bool {
if !push {
return false
}
})

errorFuncs := stringSet{}
insp.Preorder(funcNodes, func(node ast.Node) {
f := node.(*ast.FuncDecl)
if isFuncReturningErr(f.Type, allTypes, errorTypes) {
errorFuncs[f.Name.Name] = struct{}{}
}
})

inspectPkgLevelVarsOnly := func(node ast.Node) bool {
switch v := node.(type) {
case *ast.FuncDecl:
return false

case *ast.ValueSpec:
if name, ok := isSentinelError(v, pkgAliases, allTypes, errorTypes, errorFuncs); ok && !isValidErrorVarName(name) {
reportAboutErrorVar(pass, v.Pos(), name)
if len(v.Names) != 1 {
return false
}
ident := v.Names[0]

if exprImplementsError(pass, ident) && !isValidErrorVarName(ident.Name) {
reportAboutSentinelError(pass, v.Pos(), ident.Name)
}
return false

case *ast.TypeSpec:
tt := pass.TypesInfo.TypeOf(v.Name)
if tt == nil {
return false
}
// NOTE(a.telyshev): Pointer is the hack against Error() method with pointer receiver.
if !typeImplementsError(types.NewPointer(tt)) {
return false
}

name := v.Name.Name
if _, ok := v.Type.(*ast.ArrayType); ok {
if !isValidErrorArrayTypeName(name) {
reportAboutErrorType(pass, v.Pos(), name, true)
}
} else if !isValidErrorTypeName(name) {
reportAboutErrorType(pass, v.Pos(), name, false)
}
return false
}

return true
}
for _, f := range pass.Files {
ast.Inspect(f, inspectPkgLevelVarsOnly)
}
})

return nil, nil //nolint:nilnil
}
Expand All @@ -113,23 +86,15 @@ func reportAboutErrorType(pass *analysis.Pass, typePos token.Pos, typeName strin
if isArrayType {
form += "s"
}
pass.Reportf(typePos, "the type name `%s` should conform to the `%s` format", typeName, form)
pass.Reportf(typePos, "the error type name `%s` should conform to the `%s` format", typeName, form)
}

func reportAboutErrorVar(pass *analysis.Pass, pos token.Pos, varName string) {
func reportAboutSentinelError(pass *analysis.Pass, pos token.Pos, varName string) {
var form string
if unicode.IsLower([]rune(varName)[0]) {
form = "errXxx"
} else {
form = "ErrXxx"
}
pass.Reportf(pos, "the variable name `%s` should conform to the `%s` format", varName, form)
}

func getPkgFromPath(p string) string {
idx := strings.LastIndex(p, "/")
if idx == -1 {
return p
}
return p[idx+1:]
pass.Reportf(pos, "the sentinel error name `%s` should conform to the `%s` format", varName, form)
}
16 changes: 0 additions & 16 deletions pkg/analyzer/analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,3 @@ func TestErrName(t *testing.T) {
}
analysistest.Run(t, analysistest.TestData(), New(), pkgs...)
}

func Test_getPkgFromPath(t *testing.T) {
for _, tt := range []struct {
in, expected string
}{
{},
{in: "errors", expected: "errors"},
{in: "github/pkg/errors", expected: "errors"},
{in: "github.com/cockroachdb/errors", expected: "errors"},
} {
pkg := getPkgFromPath(tt.in)
if pkg != tt.expected {
t.Errorf("%q != %q", pkg, tt.expected)
}
}
}
Loading

0 comments on commit 2ed8830

Please sign in to comment.