Skip to content

Commit 24ec80b

Browse files
Release v0.2.0
- Reorganized project - Separated linter into passes - Improved branch analysis - Added config pass - Fixed concurrency issues - Fixed repeated package loading - Fixed documentation - Fixed testing - Fixed invalid behavior
1 parent 0bcd71d commit 24ec80b

File tree

45 files changed

+1292
-982
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1292
-982
lines changed

.custom-gcl.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
version: v1.64.0
22

3-
destination: ./errstack/testdata/src
3+
destination: ./testdata/src
44

55
plugins:
66
# a plugin from a Go proxy
77
# - module: 'github.com/AdamBrianBright/errstack'
8-
# import: 'github.com/AdamBrianBright/errstack'
9-
# version: v1.0.0
8+
# import: 'github.com/AdamBrianBright/errstack/cmd/gclplugin'
9+
# version: v0.2.0
1010

1111
# a plugin from local source
1212
- module: 'github.com/AdamBrianBright/errstack'
13-
import: 'github.com/AdamBrianBright/errstack/errstack'
13+
import: 'github.com/AdamBrianBright/errstack/cmd/gclplugin'
1414
path: .

README.md

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,57 @@
11
# ErrStack
22

3-
**ErrStack** is a linter for Go that checks for unnecessary error wrapping using `errors.Wrap`, `errors.Wrapf`, and `errors.WithStack`.
3+
**ErrStack** is a linter for Go that checks for unnecessary error wrapping using `errors.Wrap`, `errors.Wrapf`, and
4+
`errors.WithStack`.
45

56
It is created as a complement to the [wrapcheck](https://github.com/tomarrell/wrapcheck) linter.
67

78
## Installation
89

9-
Go `>= v1.21`
10+
Go `>= v1.22`
1011

1112
```bash
1213
go install github.com/AdamBrianBright/errstack/cmd/errstack@latest
1314
```
1415

1516
ErrStack can be used as a module for [golangci-lint](https://golangci-lint.run/usage/linters/#modules).
1617

18+
`.custom-gcl.yml`
19+
```yaml .custom-gcl.yml
20+
version: v1.64.0
21+
22+
destination: ./testdata/src
23+
24+
plugins:
25+
- module: 'github.com/AdamBrianBright/errstack'
26+
import: 'github.com/AdamBrianBright/errstack/cmd/gclplugin'
27+
version: v0.2.0
28+
```
29+
30+
`.golangci.yml`
31+
```yaml .golangci.yml
32+
linters-settings:
33+
custom:
34+
errstack:
35+
type: "module"
36+
description: Walks through the AST and finds all functions that return an error.
37+
settings:
38+
wrapperFunctions:
39+
- pkg: github.com/pkg/errors
40+
names: [ New, Errorf, Wrap, Wrapf, WithStack ]
41+
cleanFunctions:
42+
- pkg: errors
43+
names: [ New ]
44+
- pkg: fmt
45+
names: [ Errorf ]
46+
- pkg: github.com/pkg/errors
47+
names: [ WithMessage, WithMessagef ]
48+
49+
linters:
50+
disable-all: true
51+
enable:
52+
- errstack
53+
```
54+
1755
## Configuration
1856

1957
You can configure ErrStack using the `.errstack.yaml` file in your project root, or in your home directory.
@@ -31,6 +69,7 @@ wrapperFunctions:
3169
- Wrap
3270
- Wrapf
3371
- WithStack
72+
# List of functions that are considered to clean errors without stacktrace.
3473
cleanFunctions:
3574
- pkg: errors
3675
names:
@@ -58,25 +97,29 @@ This linter is tested using `analysistest`, you can view all the test cases unde
5897

5998
## Why?
6099

61-
If you're using some fancy error wrapping library like [github.com/pkg/errors](https://pkg.go.dev/github.com/pkg/errors), you may have stumbled upon doubling or tripling the amount of stacktrace duplicates in your logs.
100+
If you're using some fancy error wrapping library
101+
like [github.com/pkg/errors](https://pkg.go.dev/github.com/pkg/errors), you may have stumbled upon doubling or tripling
102+
the amount of stacktrace duplicates in your logs.
62103

63-
This happens because the library wraps errors in context style, hiding stacktraces from the user in unexported structs and fields like russian dolls.
104+
This happens because the library wraps errors in context style, hiding stacktraces from the user in unexported structs
105+
and fields like russian dolls.
64106

65-
When doing so, libraries don't check for stacktraces already present in the error, since it is usually not necessary and only slows down your code.
107+
When doing so, libraries don't check for stacktraces already present in the error, since it is usually not necessary and
108+
only slows down your code.
66109

67-
However, if you're using libraries out of your control, you may not be able to easily identify whether some functions may return wrapped errors or not, and just wrap errors from external packages like [wrapcheck](https://github.com/tomarrell/wrapcheck) suggests anyways.
110+
However, if you're using libraries out of your control, you may not be able to easily identify whether some functions
111+
may return wrapped errors or not, and just wrap errors from external packages
112+
like [wrapcheck](https://github.com/tomarrell/wrapcheck) suggests anyways.
68113

69114
This linter helps you to identify such cases, and help you remove unnecessary wrapping.
70115

71116
## How does it work?
72117

73-
ErrStack finds all calls to configured list of wrapping functions in your code and finds the source of the error.
74-
75-
When the source of an error is located up to it's root (assigment statement, or return statement with errors.New() passed as an argument), it check if the error was wrapped, excluding nil errors.
76-
77-
!!! This linter doesn't verify the actual types as it's almost impossible to do so and usually pointless.
78-
79-
Linter calculates the amount of non-nil branches and prints a warning if it's greater than the configured threshold.
118+
1. Preloads all packages and parses their ASTs.
119+
2. Finds all functions that return errors.
120+
3. Finds all calls to functions that return errors.
121+
4. Marks functions that return wrapped errors.
122+
5. Analyzes original function CFG and reports if unnecessary wrapping is used.
80123

81124
### Example
82125

@@ -92,6 +135,6 @@ func main() {
92135
}
93136
94137
func testDoubleReturnWrapStack() error {
95-
return errors.Wrap(errors.WithStack(nil), "wrapped") // want `WithStack call unnecessarily wraps error with stacktrace. Replace with errors.WithMessage()`
138+
return errors.Wrap(errors.WithStack(nil), "wrapped") // want `Wrap call unnecessarily wraps error with stacktrace\. Replace with errors\.WithMessage\(\) or fmt\.Errorf\(\)`
96139
}
97140
```

Taskfile.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,27 @@ tasks:
55
cmd: go build -v -o ./build/errstack -ldflags "-s -w" ./cmd/errstack
66
test:
77
cmds:
8-
- go test -v -count=1 -cpuprofile=cpu.prof -memprofile=mem.prof -benchmem -bench=. ./errstack
8+
- go test -v -count=1 -cpuprofile=cpu.prof -memprofile=mem.prof -benchmem -bench=. .
99
lint:
10-
cmd: golangci-lint run ./errstack/... ./cmd/...
10+
cmd: golangci-lint run ./...
1111
fmt:
1212
cmd: go fmt ./...
1313
tidy:
1414
cmd: go mod tidy
1515
clean:
1616
silent: true
17-
cmd: rm -rf bin cpu.prof mem.prof errstack.test
17+
cmd: rm -rf bin cpu.prof mem.prof errstack.test errstack.log
1818
install/lint:
1919
cmd: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
2020
install/self:
2121
cmd: go install ./cmd/errstack
2222
install/custom-gcl:
2323
cmd: golangci-lint custom
2424
sources: [ ./**/*.go ]
25-
generates: [ ./errstack/testdata/src/custom-gcl ]
25+
generates: [ ./testdata/src/custom-gcl ]
2626
run:
2727
cmds:
2828
- ./custom-gcl cache clean
2929
- ./custom-gcl run ./return_mixed_numbers/...
30-
dir: errstack/testdata/src
30+
dir: testdata/src
3131
deps: [ install/custom-gcl ]

cmd/errstack/main.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import (
44
"errors"
55
"log"
66

7-
"github.com/AdamBrianBright/errstack/errstack"
7+
"github.com/AdamBrianBright/errstack/internal/config"
8+
"github.com/AdamBrianBright/errstack/internal/passes/errstack"
89

910
"github.com/spf13/viper"
1011
"golang.org/x/tools/go/analysis/singlechecker"
12+
"gopkg.in/yaml.v3"
1113
)
1214

1315
func main() {
@@ -16,8 +18,8 @@ func main() {
1618
viper.AddConfigPath("$HOME/.errstack")
1719
viper.AddConfigPath(".")
1820

19-
viper.SetDefault("wrapperFunctions", errstack.DefaultWrapperFunctions)
20-
viper.SetDefault("cleanFunctions", errstack.DefaultCleanFunctions)
21+
viper.SetDefault("wrapperFunctions", config.DefaultWrapperFunctions)
22+
viper.SetDefault("cleanFunctions", config.DefaultCleanFunctions)
2123

2224
// Read in config, ignore if the file isn't found and use defaults.
2325
if err := viper.ReadInConfig(); err != nil {
@@ -27,10 +29,18 @@ func main() {
2729
}
2830
}
2931

30-
var cfg errstack.Config
32+
var cfg config.Config
3133
if err := viper.Unmarshal(&cfg); err != nil {
3234
log.Fatalf("failed to unmarshal config: %v", err)
3335
}
36+
configYaml, err := yaml.Marshal(cfg)
37+
if err != nil {
38+
log.Fatalf("failed to marshal config: %v", err)
39+
}
40+
err = config.Analyzer.Flags.Set(config.YamlConfig, string(configYaml))
41+
if err != nil {
42+
log.Fatalf("failed to set config flag: %v", err)
43+
}
3444

35-
singlechecker.Main(errstack.NewAnalyzer(cfg))
45+
singlechecker.Main(errstack.Analyzer)
3646
}

cmd/gclplugin/gclplugin.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Package gclplugin implements the golangci-lint's module plugin interface for ErrStack to be used
2+
// as a private linter in golangci-lint. See more details at
3+
// https://golangci-lint.run/plugins/module-plugins/.
4+
package gclplugin
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/AdamBrianBright/errstack/internal/config"
10+
"github.com/AdamBrianBright/errstack/internal/passes/errstack"
11+
12+
"github.com/golangci/plugin-module-register/register"
13+
"golang.org/x/tools/go/analysis"
14+
"gopkg.in/yaml.v3"
15+
)
16+
17+
func init() {
18+
// Регистрируем кастомный линтер в реестре плагинов golangci-lint.
19+
register.Plugin("errstack", New)
20+
}
21+
22+
// New returns the golangci-lint plugin that wraps the ErrStack analyzer.
23+
func New(settings any) (register.LinterPlugin, error) {
24+
conf, err := register.DecodeSettings[*config.Config](settings)
25+
if err != nil {
26+
return nil, err
27+
}
28+
29+
return &ErrStackPlugin{conf: conf}, nil
30+
}
31+
32+
// ErrStackPlugin is the ErrStack plugin wrapper for golangci-lint.
33+
type ErrStackPlugin struct {
34+
conf *config.Config
35+
}
36+
37+
// BuildAnalyzers builds the ErrStack analyzer with the configurations applied to the config analyzer.
38+
func (p *ErrStackPlugin) BuildAnalyzers() ([]*analysis.Analyzer, error) {
39+
conf, err := yaml.Marshal(p.conf)
40+
if err != nil {
41+
return nil, fmt.Errorf("marshal config: %w", err)
42+
}
43+
err = config.Analyzer.Flags.Set(config.YamlConfig, string(conf))
44+
if err != nil {
45+
return nil, fmt.Errorf("set config flag: %w", err)
46+
}
47+
48+
return []*analysis.Analyzer{errstack.Analyzer}, nil
49+
}
50+
51+
// GetLoadMode returns the load mode of the ErrStack plugin (requiring types info).
52+
func (p *ErrStackPlugin) GetLoadMode() string {
53+
return register.LoadModeTypesInfo
54+
}

0 commit comments

Comments
 (0)