Skip to content

Commit 5d0367b

Browse files
committed
better diagnostics, add basic mode; add errors.Is
Signed-off-by: Oliver Eikemeier <[email protected]>
1 parent d8515ad commit 5d0367b

19 files changed

+585
-119
lines changed

.envrc

-1
This file was deleted.

.github/codecov.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ coverage:
44
project: false
55
patch: false
66
ignore:
7-
- atomic/nocopy.go
7+
- main.go

.github/workflows/test.yml

+38-38
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,42 @@
11
---
22
name: Test
33
"on":
4-
push:
5-
branches:
6-
- main
7-
pull_request:
8-
branches:
9-
- main
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
1010
jobs:
11-
test:
12-
name: Test on Go ${{ matrix.go }}
13-
permissions:
14-
checks: write
15-
contents: read
16-
pull-requests: read
17-
statuses: write
18-
runs-on: ubuntu-24.04
19-
strategy:
20-
matrix:
21-
go: ["1.22", "1.21"]
22-
env:
23-
GOTOOLCHAIN: local
24-
steps:
25-
- name: ✔ Check out
26-
uses: actions/checkout@v4
27-
- name: 🐹 Set up Go ${{ matrix.go }}
28-
uses: actions/setup-go@v5
29-
with:
30-
go-version: ${{ matrix.go }}
31-
check-latest: true
32-
- name: 🧸 golangci-lint
33-
uses: golangci/golangci-lint-action@v6
34-
with:
35-
version: v1.59.1
36-
- name: 🔨 Test
37-
run: go test -coverprofile=cover.out ./...
38-
- name: 🧑🏻‍💻 codecov
39-
uses: codecov/codecov-action@v4
40-
with:
41-
files: ./cover.out
42-
token: ${{ secrets.CODECOV_TOKEN }}
11+
test:
12+
name: Test on Go ${{ matrix.go }}
13+
permissions:
14+
checks: write
15+
contents: read
16+
pull-requests: read
17+
statuses: write
18+
runs-on: ubuntu-24.04
19+
strategy:
20+
matrix:
21+
go: ["1.22", "1.21"]
22+
env:
23+
GOTOOLCHAIN: local
24+
steps:
25+
- name: ✔ Check out
26+
uses: actions/checkout@v4
27+
- name: 🐹 Set up Go ${{ matrix.go }}
28+
uses: actions/setup-go@v5
29+
with:
30+
go-version: ${{ matrix.go }}
31+
check-latest: true
32+
- name: 🧸 golangci-lint
33+
uses: golangci/golangci-lint-action@v6
34+
with:
35+
version: v1.59.1
36+
- name: 🔨 Test
37+
run: go test -coverprofile=cover.out ./...
38+
- name: 🧑🏻‍💻 codecov
39+
uses: codecov/codecov-action@v4
40+
with:
41+
files: ./cover.out
42+
token: ${{ secrets.CODECOV_TOKEN }}

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Usage: `zerolint [-flag] [package]`
2020
Flags:
2121

2222
- **-c** int display offending line with this many lines of context (default -1)
23-
- **-excluded** `<filename>` read exluded types from this file
23+
- **-basic** basic analysis only
24+
- **-excluded** `<filename>` read excluded types from this file
2425
- **-zerotrace** trace found zero-sized types
2526
- **-fix** apply all suggested fixes

main.go

+59
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,72 @@
1414
//
1515
// SPDX-License-Identifier: Apache-2.0
1616

17+
// This is the main program for the zerolint linter.
1718
package main
1819

1920
import (
21+
"flag"
22+
"fmt"
23+
"os"
24+
"runtime/debug"
25+
2026
"fillmore-labs.com/zerolint/pkg/analyzer"
2127
"golang.org/x/tools/go/analysis/singlechecker"
2228
)
2329

2430
func main() {
31+
a := analyzer.Analyzer
32+
addVersionFlag(&a.Flags)
2533
singlechecker.Main(analyzer.Analyzer)
2634
}
35+
36+
func addVersionFlag(s *flag.FlagSet) {
37+
if s.Lookup("V") == nil {
38+
s.Var(versionFlag{}, "V", "print version and exit")
39+
}
40+
}
41+
42+
type versionFlag struct{}
43+
44+
func (versionFlag) IsBoolFlag() bool { return true }
45+
func (versionFlag) Get() any { return nil }
46+
func (versionFlag) String() string { return "" }
47+
func (versionFlag) Set(_ string) error {
48+
progname, err := os.Executable()
49+
if err != nil {
50+
return err
51+
}
52+
53+
var goVersion, version, revision, time string
54+
if bi, ok := debug.ReadBuildInfo(); ok {
55+
goVersion = bi.GoVersion
56+
version = bi.Main.Version
57+
var modified string
58+
for _, s := range bi.Settings {
59+
switch s.Key {
60+
case "vcs.revision":
61+
revision = s.Value
62+
63+
case "vcs.time":
64+
time = s.Value
65+
66+
case "vcs.modified":
67+
modified = s.Value
68+
}
69+
}
70+
71+
if len(revision) > 6 { //nolint:mnd
72+
revision = revision[:7]
73+
if len(modified) > 0 {
74+
revision += " (dirty)"
75+
}
76+
}
77+
}
78+
79+
fmt.Printf("%s version %s build with %s from %s on %s\n",
80+
progname, version, goVersion, revision, time)
81+
82+
os.Exit(0)
83+
84+
return nil
85+
}

pkg/analyzer/analyzer.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,26 @@ var Analyzer = &analysis.Analyzer{ //nolint:gochecknoglobals
4040
func init() { //nolint:gochecknoinits
4141
Analyzer.Flags.StringVar(&Excludes, "excluded", "", "read excluded types from this file")
4242
Analyzer.Flags.BoolVar(&ZeroTrace, "zerotrace", false, "trace found zero-sized types")
43+
Analyzer.Flags.BoolVar(&Basic, "basic", false, "basic analysis only")
4344
}
4445

45-
var ZeroTrace bool //nolint:gochecknoglobals
46+
var (
47+
ZeroTrace bool //nolint:gochecknoglobals
48+
Basic bool //nolint:gochecknoglobals
49+
)
4650

4751
func run(pass *analysis.Pass) (any, error) {
4852
excludes, err := ReadExcludes()
4953
if err != nil {
5054
return nil, err
5155
}
5256

53-
v := visitor.Visitor{Pass: pass, Excludes: excludes, ZeroTrace: ZeroTrace}
57+
v := visitor.Visitor{
58+
Pass: pass,
59+
Excludes: excludes,
60+
ZeroTrace: ZeroTrace,
61+
Basic: Basic,
62+
}
5463
v.Run()
5564

5665
return any(nil), nil

pkg/analyzer/analyzer_test.go

+5
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,10 @@ func TestAnalyzer(t *testing.T) { //nolint:paralleltest
2727
dir := analysistest.TestData()
2828
a := analyzer.Analyzer
2929

30+
analyzer.Basic = true
31+
analysistest.Run(t, dir, a, "basic")
32+
33+
analyzer.Basic = false
34+
analyzer.Excludes = dir + "/excluded.txt"
3035
analysistest.RunWithSuggestedFixes(t, dir, a, "a")
3136
}

pkg/analyzer/testdata/excluded.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# zerolint exclusions for a
2+
a.C

pkg/analyzer/testdata/src/a/testdata.go

+79-1
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,59 @@
1717
package a
1818

1919
import (
20+
"errors"
2021
"fmt"
2122
)
2223

24+
type empty struct{}
25+
26+
type typedError[T any] struct {
27+
_ [0]T
28+
}
29+
30+
func (*typedError[_]) Error() string { // want "pointer to zero-size type"
31+
return "an error"
32+
}
33+
34+
var (
35+
_ error = &typedError[any]{} // want "address of zero-size variable"
36+
ErrOne = &(typedError[int]{}) // want "address of zero-size variable"
37+
ErrTwo = (new)(typedError[float64]) // want "new called on zero-size type"
38+
)
39+
40+
type myErrors struct{}
41+
42+
func (myErrors) Is(err, target error) bool {
43+
return false
44+
}
45+
46+
var myErrs = myErrors{}
47+
2348
func Exported() {
2449
var x [0]string
2550
var y [0]string
2651

52+
if errors.Is(nil, ErrOne) {
53+
fmt.Println("nil")
54+
}
55+
56+
if myErrs.Is(ErrOne, ErrTwo) {
57+
fmt.Println("nil")
58+
}
59+
60+
if errors.Is(func() error { // want "comparison of pointer to zero-size variable"
61+
return ErrOne
62+
}(), ErrTwo) {
63+
fmt.Println("equal")
64+
}
65+
66+
var err *typedError[int] // want "pointer to zero-size type"
67+
_ = errors.As(ErrOne, &err)
68+
69+
_ = (new)(struct{}) // want "new called on zero-size type"
70+
71+
_ = new(empty) // want "new called on zero-size type"
72+
2773
xp, yp := &x, &y // want "address of zero-size variable" "address of zero-size variable"
2874

2975
_ = *xp // want "pointer to zero-size variable"
@@ -36,6 +82,10 @@ func Exported() {
3682
fmt.Println("not equal")
3783
}
3884

85+
if xp == nil {
86+
fmt.Println("nil")
87+
}
88+
3989
_, _ = any(xp).((*[0]string)) // want "pointer to zero-size type"
4090

4191
switch any(xp).(type) {
@@ -44,6 +94,16 @@ func Exported() {
4494
}
4595
}
4696

97+
func Undiagnosed() {
98+
i := 1 + 1
99+
i = *&i
100+
101+
f := func(int) {}
102+
f(i)
103+
104+
_ = !false
105+
}
106+
47107
type A [0]string
48108

49109
type B = A
@@ -62,7 +122,9 @@ func (g *greeter) String() string { // want "pointer to zero-size type"
62122

63123
var _ fmt.Stringer = &greeter{} // want "address of zero-size variable"
64124

65-
var _ fmt.Stringer = (*greeter)(nil) // want "cast to pointer to zero-size variable"
125+
var _ fmt.Stringer = (*greeter)(nil) // want "cast of nil to pointer to zero-size variable"
126+
127+
var _ fmt.Stringer = new(greeter) // want "new called on zero-size type"
66128

67129
type greeter2[T any] [5][5][0]T
68130

@@ -71,3 +133,19 @@ func (g *greeter2[T]) String() string { // want "pointer to zero-size type"
71133
}
72134

73135
var _ fmt.Stringer = &greeter2[int]{} // want "address of zero-size variable"
136+
137+
type C struct{}
138+
139+
func (*C) String() string {
140+
return "hello, world"
141+
}
142+
143+
var _ fmt.Stringer = (*C)(nil)
144+
145+
type D struct{ _ int }
146+
147+
func (*D) String() string {
148+
return "hello, world"
149+
}
150+
151+
var _ fmt.Stringer = (*D)(nil)

0 commit comments

Comments
 (0)