Skip to content

Commit d23c354

Browse files
jjtildez
andauthored
feat: add spancheck linter (#4290)
Co-authored-by: Fernandez Ludovic <[email protected]>
1 parent 0fdf33a commit d23c354

File tree

12 files changed

+402
-1
lines changed

12 files changed

+402
-1
lines changed

.golangci.reference.yml

+20
Original file line numberDiff line numberDiff line change
@@ -1912,6 +1912,24 @@ linters-settings:
19121912
# Default: false
19131913
args-on-sep-lines: true
19141914

1915+
spancheck:
1916+
# Checks to enable.
1917+
# Options include:
1918+
# - `end`: check that `span.End()` is called
1919+
# - `record-error`: check that `span.RecordError(err)` is called when an error is returned
1920+
# - `set-status`: check that `span.SetStatus(codes.Error, msg)` is called when an error is returned
1921+
# Default: ["end"]
1922+
checks:
1923+
- end
1924+
- record-error
1925+
- set-status
1926+
# A list of regexes for function signatures that silence `record-error` and `set-status` reports
1927+
# if found in the call path to a returned error.
1928+
# https://github.com/jjti/go-spancheck#ignore-check-signatures
1929+
# Default: []
1930+
ignore-check-signatures:
1931+
- "telemetry.RecordError"
1932+
19151933
staticcheck:
19161934
# Deprecated: use the global `run.go` instead.
19171935
go: "1.15"
@@ -2442,6 +2460,7 @@ linters:
24422460
- rowserrcheck
24432461
- scopelint
24442462
- sloglint
2463+
- spancheck
24452464
- sqlclosecheck
24462465
- staticcheck
24472466
- structcheck
@@ -2562,6 +2581,7 @@ linters:
25622581
- rowserrcheck
25632582
- scopelint
25642583
- sloglint
2584+
- spancheck
25652585
- sqlclosecheck
25662586
- staticcheck
25672587
- structcheck

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ require (
5757
github.com/jgautheron/goconst v1.7.0
5858
github.com/jingyugao/rowserrcheck v1.1.1
5959
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af
60+
github.com/jjti/go-spancheck v0.4.2
6061
github.com/julz/importas v0.1.0
6162
github.com/kisielk/errcheck v1.6.3
6263
github.com/kkHAIKE/contextcheck v1.1.4

go.sum

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/config/linters_settings.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,14 @@ type LintersSettings struct {
245245
Revive ReviveSettings
246246
RowsErrCheck RowsErrCheckSettings
247247
SlogLint SlogLintSettings
248+
Spancheck SpancheckSettings
248249
Staticcheck StaticCheckSettings
249250
Structcheck StructCheckSettings
250251
Stylecheck StaticCheckSettings
251252
TagAlign TagAlignSettings
252253
Tagliatelle TagliatelleSettings
253-
Testifylint TestifylintSettings
254254
Tenv TenvSettings
255+
Testifylint TestifylintSettings
255256
Testpackage TestpackageSettings
256257
Thelper ThelperSettings
257258
Unparam UnparamSettings
@@ -773,6 +774,11 @@ type SlogLintSettings struct {
773774
ArgsOnSepLines bool `mapstructure:"args-on-sep-lines"`
774775
}
775776

777+
type SpancheckSettings struct {
778+
Checks []string `mapstructure:"checks"`
779+
IgnoreCheckSignatures []string `mapstructure:"ignore-check-signatures"`
780+
}
781+
776782
type StaticCheckSettings struct {
777783
// Deprecated: use the global `run.go` instead.
778784
GoVersion string `mapstructure:"go"`

pkg/golinters/spancheck.go

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package golinters
2+
3+
import (
4+
"github.com/jjti/go-spancheck"
5+
"golang.org/x/tools/go/analysis"
6+
7+
"github.com/golangci/golangci-lint/pkg/config"
8+
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
9+
)
10+
11+
func NewSpancheck(settings *config.SpancheckSettings) *goanalysis.Linter {
12+
cfg := spancheck.NewDefaultConfig()
13+
14+
if settings != nil {
15+
if settings.Checks != nil {
16+
cfg.EnabledChecks = settings.Checks
17+
}
18+
19+
if settings.IgnoreCheckSignatures != nil {
20+
cfg.IgnoreChecksSignaturesSlice = settings.IgnoreCheckSignatures
21+
}
22+
}
23+
24+
a := spancheck.NewAnalyzerWithConfig(cfg)
25+
26+
return goanalysis.
27+
NewLinter(a.Name, a.Doc, []*analysis.Analyzer{a}, nil).
28+
WithLoadMode(goanalysis.LoadModeTypesInfo)
29+
}

pkg/lint/lintersdb/manager.go

+8
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
131131
reviveCfg *config.ReviveSettings
132132
rowserrcheckCfg *config.RowsErrCheckSettings
133133
sloglintCfg *config.SlogLintSettings
134+
spancheckCfg *config.SpancheckSettings
134135
staticcheckCfg *config.StaticCheckSettings
135136
structcheckCfg *config.StructCheckSettings
136137
stylecheckCfg *config.StaticCheckSettings
@@ -216,6 +217,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
216217
reviveCfg = &m.cfg.LintersSettings.Revive
217218
rowserrcheckCfg = &m.cfg.LintersSettings.RowsErrCheck
218219
sloglintCfg = &m.cfg.LintersSettings.SlogLint
220+
spancheckCfg = &m.cfg.LintersSettings.Spancheck
219221
staticcheckCfg = &m.cfg.LintersSettings.Staticcheck
220222
structcheckCfg = &m.cfg.LintersSettings.Structcheck
221223
stylecheckCfg = &m.cfg.LintersSettings.Stylecheck
@@ -782,6 +784,12 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
782784
WithLoadForGoAnalysis().
783785
WithURL("https://github.com/ryanrolds/sqlclosecheck"),
784786

787+
linter.NewConfig(golinters.NewSpancheck(spancheckCfg)).
788+
WithSince("v1.56.0").
789+
WithLoadForGoAnalysis().
790+
WithPresets(linter.PresetBugs).
791+
WithURL("https://github.com/jjti/go-spancheck"),
792+
785793
linter.NewConfig(golinters.NewStaticcheck(staticcheckCfg)).
786794
WithEnabledByDefault().
787795
WithSince("v1.0.0").

test/linters_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func TestSourcesFromTestdataSubDir(t *testing.T) {
3333
"ginkgolinter",
3434
"zerologlint",
3535
"protogetter",
36+
"spancheck",
3637
}
3738

3839
for _, dir := range subDirs {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
linters-settings:
2+
spancheck:
3+
checks:
4+
- "end"
5+
- "record-error"
6+
- "set-status"
7+
ignore-check-signatures:
8+
- "recordErr"

test/testdata/spancheck/go.mod

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module spancheck
2+
3+
go 1.20
4+
5+
require (
6+
go.opentelemetry.io/otel v1.21.0
7+
go.opentelemetry.io/otel/trace v1.21.0
8+
)
9+
10+
require (
11+
github.com/go-logr/logr v1.4.1 // indirect
12+
github.com/go-logr/stdr v1.2.2 // indirect
13+
go.opentelemetry.io/otel/metric v1.21.0 // indirect
14+
)

test/testdata/spancheck/go.sum

+16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//golangcitest:args -Espancheck
2+
package spancheck
3+
4+
import (
5+
"context"
6+
"errors"
7+
"fmt"
8+
9+
"go.opentelemetry.io/otel"
10+
"go.opentelemetry.io/otel/codes"
11+
)
12+
13+
type testDefaultError struct{}
14+
15+
func (e *testDefaultError) Error() string {
16+
return "foo"
17+
}
18+
19+
// incorrect
20+
21+
func _() {
22+
otel.Tracer("foo").Start(context.Background(), "bar") // want "span is unassigned, probable memory leak"
23+
ctx, _ := otel.Tracer("foo").Start(context.Background(), "bar") // want "span is unassigned, probable memory leak"
24+
fmt.Print(ctx)
25+
}
26+
27+
func _() {
28+
ctx, span := otel.Tracer("foo").Start(context.Background(), "bar") // want "span.End is not called on all paths, possible memory leak"
29+
print(ctx.Done(), span.IsRecording())
30+
} // want "return can be reached without calling span.End"
31+
32+
func _() {
33+
var ctx, span = otel.Tracer("foo").Start(context.Background(), "bar") // want "span.End is not called on all paths, possible memory leak"
34+
print(ctx.Done(), span.IsRecording())
35+
} // want "return can be reached without calling span.End"
36+
37+
func _() {
38+
_, span := otel.Tracer("foo").Start(context.Background(), "bar") // want "span.End is not called on all paths, possible memory leak"
39+
_, span = otel.Tracer("foo").Start(context.Background(), "bar")
40+
fmt.Print(span)
41+
defer span.End()
42+
} // want "return can be reached without calling span.End"
43+
44+
// correct
45+
46+
func _() error {
47+
_, span := otel.Tracer("foo").Start(context.Background(), "bar")
48+
defer span.End()
49+
50+
return nil
51+
}
52+
53+
func _() error {
54+
_, span := otel.Tracer("foo").Start(context.Background(), "bar")
55+
defer span.End()
56+
57+
if true {
58+
return nil
59+
}
60+
61+
return nil
62+
}
63+
64+
func _() error {
65+
_, span := otel.Tracer("foo").Start(context.Background(), "bar")
66+
defer span.End()
67+
68+
if false {
69+
err := errors.New("foo")
70+
span.SetStatus(codes.Error, err.Error())
71+
span.RecordError(err)
72+
return err
73+
}
74+
75+
if true {
76+
span.SetStatus(codes.Error, "foo")
77+
span.RecordError(errors.New("foo"))
78+
return errors.New("bar")
79+
}
80+
81+
return nil
82+
}
83+
84+
func _() {
85+
_, span := otel.Tracer("foo").Start(context.Background(), "bar")
86+
defer span.End()
87+
88+
_, span = otel.Tracer("foo").Start(context.Background(), "bar")
89+
defer span.End()
90+
}

0 commit comments

Comments
 (0)