Skip to content

Commit 734db16

Browse files
authoredJul 2, 2021
tools: Add internal linter, lint fxevent.Loggers (uber-go#752)
With uber-go#733, we have two fxevent.Logger implementations in Fx, and we plan to change the event types a fair bit in the near future. Rather than manually verify that the two linters are in-sync and handle all event types, it's preferable to verify this automatically. Add a custom linter based on go/analysis meant for use only inside Fx. This linter verifies that any fxevent.Logger implementations it finds handle all fxevent.Event types, or none of them. For testing with analysistest, this creates a fake fxevent package similar to the real fxevent package, and lints code that uses it. Refs GO-690
1 parent adadee5 commit 734db16

File tree

17 files changed

+542
-17
lines changed

17 files changed

+542
-17
lines changed
 

‎Makefile

+26-8
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ export GOBIN ?= $(shell pwd)/bin
22

33
GOLINT = $(GOBIN)/golint
44
STATICCHECK = $(GOBIN)/staticcheck
5+
FXLINT = $(GOBIN)/fxlint
56

67
GO_FILES := $(shell \
7-
find . '(' -path '*/.*' -o -path './vendor' ')' -prune \
8+
find . '(' -path '*/.*' -o -path './vendor' -o -path '*/testdata/*' ')' -prune \
89
-o -name '*.go' -print | cut -b3-)
910

11+
MODULES = . ./tools
12+
1013
.PHONY: build
1114
build:
1215
go build ./...
@@ -17,32 +20,47 @@ install:
1720

1821
.PHONY: test
1922
test:
20-
go test -race ./...
23+
@$(foreach dir,$(MODULES),(cd $(dir) && go test -race ./...) &&) true
2124

2225
.PHONY: cover
2326
cover:
2427
go test -race -coverprofile=cover.out -coverpkg=./... ./...
2528
go tool cover -html=cover.out -o cover.html
2629

27-
$(GOLINT):
30+
$(GOLINT): tools/go.mod
2831
cd tools && go install golang.org/x/lint/golint
2932

30-
$(STATICCHECK):
33+
$(STATICCHECK): tools/go.mod
3134
cd tools && go install honnef.co/go/tools/cmd/staticcheck
3235

36+
$(FXLINT): $(shell find tools -name '*.go')
37+
cd tools && go install go.uber.org/fx/tools/cmd/fxlint
38+
3339
.PHONY: lint
34-
lint: $(GOLINT) $(STATICCHECK)
40+
lint: $(GOLINT) $(STATICCHECK) $(FXLINT)
3541
@rm -rf lint.log
3642
@echo "Checking formatting..."
3743
@gofmt -d -s $(GO_FILES) 2>&1 | tee lint.log
3844
@echo "Checking vet..."
39-
@go vet ./... 2>&1 | tee -a lint.log
45+
@$(foreach dir,$(MODULES),(cd $(dir) && go vet ./... 2>&1) &&) true | tee -a lint.log
4046
@echo "Checking lint..."
41-
@$(GOLINT) ./... | tee -a lint.log
47+
@$(foreach dir,$(MODULES),(cd $(dir) && $(GOLINT) ./... 2>&1) &&) true | tee -a lint.log
4248
@echo "Checking staticcheck..."
43-
@$(STATICCHECK) ./... | tee -a lint.log
49+
@$(foreach dir,$(MODULES),(cd $(dir) && $(STATICCHECK) ./... 2>&1) &&) true | tee -a lint.log
50+
@echo "Checking fxlint..."
51+
@$(FXLINT) ./... | tee -a lint.log
4452
@echo "Checking for unresolved FIXMEs..."
4553
@git grep -i fixme | grep -v -e vendor -e Makefile -e .md | tee -a lint.log
4654
@echo "Checking for license headers..."
4755
@./checklicense.sh | tee -a lint.log
4856
@[ ! -s lint.log ]
57+
@echo "Checking 'go mod tidy'..."
58+
@make tidy
59+
@if ! git diff --quiet; then \
60+
echo "'go mod tidy' resulted in changes or working tree is dirty:"; \
61+
git --no-pager diff; \
62+
fi
63+
64+
.PHONY: tidy
65+
tidy:
66+
@$(foreach dir,$(MODULES),(cd $(dir) && go mod tidy) &&) true

‎checklicense.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ do
1212
(( ERROR_COUNT++ ))
1313
;;
1414
esac
15-
done < <(git ls-files "*\.go")
15+
done < <(git ls-files "*\.go" | grep -v /testdata/)
1616

1717
exit $ERROR_COUNT
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
// Copyright (c) 2021 Uber Technologies, Inc.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
// Package allfxevents implements a Go analysis pass that verifies that an
22+
// fxevent.Logger implementation handles all known fxevent types. As a special
23+
// case for no-op or fake fxevent.Loggers, it ignores implementations that
24+
// handle none of the event types.
25+
//
26+
// This is meant for use within Fx only.
27+
package allfxevents
28+
29+
import (
30+
"fmt"
31+
"go/ast"
32+
"go/token"
33+
"go/types"
34+
"sort"
35+
"strings"
36+
37+
"golang.org/x/tools/go/analysis"
38+
"golang.org/x/tools/go/analysis/passes/inspect"
39+
"golang.org/x/tools/go/ast/inspector"
40+
"golang.org/x/tools/go/types/typeutil"
41+
)
42+
43+
// Analyzer is a go/analysis compatible analyzer that verifies that all
44+
// fxevent.Loggers shipped with Fx handle all known Fx event types.
45+
var Analyzer = &analysis.Analyzer{
46+
Name: "allfxevents",
47+
Doc: "check for unhandled fxevent.Events",
48+
Run: run,
49+
Requires: []*analysis.Analyzer{
50+
inspect.Analyzer,
51+
},
52+
}
53+
54+
var _filter = []ast.Node{
55+
&ast.File{},
56+
&ast.FuncDecl{},
57+
&ast.CaseClause{},
58+
&ast.TypeAssertExpr{},
59+
}
60+
61+
func run(pass *analysis.Pass) (interface{}, error) {
62+
fxeventPkg, ok := findPackage(pass.Pkg, "go.uber.org/fx/fxevent")
63+
if !ok {
64+
// If the package doesn't import fxevent, and itself isn't
65+
// fxevent, then we don't need to run this pass.
66+
return nil, nil
67+
}
68+
69+
v := visitor{
70+
Fxevent: inspectFxevent(fxeventPkg),
71+
Fset: pass.Fset,
72+
Info: pass.TypesInfo,
73+
Report: pass.Report,
74+
}
75+
76+
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes(_filter, v.Visit)
77+
return nil, nil
78+
}
79+
80+
type visitor struct {
81+
Fset *token.FileSet
82+
Info *types.Info
83+
Fxevent fxevent
84+
Report func(analysis.Diagnostic)
85+
86+
// types not yet referenced by this function
87+
loggerType types.Type
88+
funcEvents *typeSet
89+
}
90+
91+
func (v *visitor) Visit(n ast.Node, push bool) (recurse bool) {
92+
switch n := n.(type) {
93+
case *ast.File:
94+
if !push {
95+
return false
96+
}
97+
98+
// Don't run the linter on test files.
99+
fname := v.Fset.File(n.Pos()).Name()
100+
return !strings.HasSuffix(fname, "_test.go")
101+
102+
case *ast.FuncDecl:
103+
if !push {
104+
return v.funcDeclExit(n)
105+
}
106+
return v.funcDeclEnter(n)
107+
108+
case *ast.CaseClause:
109+
if !push {
110+
return false
111+
}
112+
for _, expr := range n.List {
113+
t := v.Info.Types[expr].Type
114+
if t != nil {
115+
v.funcEvents.Remove(t)
116+
}
117+
}
118+
119+
case *ast.TypeAssertExpr:
120+
if !push {
121+
return false
122+
}
123+
t := v.Info.Types[n.Type].Type
124+
if t != nil {
125+
v.funcEvents.Remove(t)
126+
}
127+
}
128+
129+
return false
130+
}
131+
132+
func (v *visitor) funcDeclEnter(n *ast.FuncDecl) bool {
133+
// Skip top-level functions, and methods not named
134+
// LogEvent.
135+
if n.Recv == nil || n.Name.Name != "LogEvent" {
136+
return false
137+
}
138+
139+
// Skip types that don't implement fxevent.Logger.
140+
t := v.Info.Types[n.Recv.List[0].Type].Type
141+
if t == nil || !types.Implements(t, v.Fxevent.LoggerInterface) {
142+
return false
143+
}
144+
145+
// Each function declaration gets its own copy of the typeSet to track
146+
// events in.
147+
v.loggerType = t
148+
v.funcEvents = v.Fxevent.Events.Clone()
149+
return true
150+
}
151+
152+
func (v *visitor) funcDeclExit(n *ast.FuncDecl) bool {
153+
nEvents := v.funcEvents.Len()
154+
if nEvents == 0 {
155+
return false
156+
}
157+
158+
// If the logger doesn't handle *any* event type, it's probably a fake,
159+
// or a no-op implementation. Don't bother with it.
160+
if nEvents == v.Fxevent.Events.Len() {
161+
return false
162+
}
163+
164+
missing := make([]string, 0, nEvents)
165+
v.funcEvents.Iterate(func(t types.Type) {
166+
// Use a fxevent qualifier so that event names don't include
167+
// the full import path of the fxevent package.
168+
missing = append(missing, types.TypeString(t, emptyQualifier))
169+
})
170+
sort.Strings(missing)
171+
172+
v.Report(analysis.Diagnostic{
173+
Pos: n.Pos(),
174+
Message: fmt.Sprintf("%v doesn't handle %v",
175+
types.TypeString(v.loggerType, emptyQualifier),
176+
missing,
177+
),
178+
})
179+
180+
return false
181+
}
182+
183+
// Find the package with the given import path.
184+
func findPackage(pkg *types.Package, importPath string) (_ *types.Package, ok bool) {
185+
if pkg.Path() == importPath {
186+
return pkg, true
187+
}
188+
189+
for _, imp := range pkg.Imports() {
190+
if imp.Path() == importPath {
191+
return imp, true
192+
}
193+
}
194+
195+
return nil, false
196+
}
197+
198+
// fxevent holds type information extracted from the fxevent package necessary
199+
// for inspection.
200+
type fxevent struct {
201+
Logger types.Type // fxevent.Logger
202+
LoggerInterface *types.Interface // raw type information for fxevent.Logger
203+
204+
Event types.Type // fxevent.Type
205+
Events typeSet
206+
}
207+
208+
func inspectFxevent(pkg *types.Package) fxevent {
209+
scope := pkg.Scope()
210+
event := scope.Lookup("Event").Type()
211+
212+
var eventTypes typeSet
213+
for _, name := range scope.Names() {
214+
if name == "Event" {
215+
continue
216+
}
217+
218+
obj := scope.Lookup(name)
219+
if !obj.Exported() {
220+
continue
221+
}
222+
223+
typ := obj.Type()
224+
225+
if !types.ConvertibleTo(typ, event) {
226+
typ = types.NewPointer(typ)
227+
if !types.ConvertibleTo(typ, event) {
228+
continue
229+
}
230+
}
231+
232+
eventTypes.Put(typ)
233+
}
234+
235+
logger := scope.Lookup("Logger").Type()
236+
return fxevent{
237+
Logger: logger,
238+
LoggerInterface: logger.Underlying().(*types.Interface),
239+
Event: event,
240+
Events: eventTypes,
241+
}
242+
}
243+
244+
// A set of types.Type objects. The zero value is valid.
245+
type typeSet struct{ m typeutil.Map }
246+
247+
func (ts *typeSet) Len() int {
248+
return ts.m.Len()
249+
}
250+
251+
// Put puts an item into the set.
252+
func (ts *typeSet) Put(t types.Type) {
253+
ts.m.Set(t, struct{}{})
254+
}
255+
256+
// Remove removes an item from the set, reporting whether it was found in the
257+
// set.
258+
func (ts *typeSet) Remove(t types.Type) (found bool) {
259+
return ts.m.Delete(t)
260+
}
261+
262+
// Iterate iterates through the type set in an unspecified order.
263+
func (ts *typeSet) Iterate(f func(types.Type)) {
264+
ts.m.Iterate(func(t types.Type, _ interface{}) {
265+
f(t)
266+
})
267+
}
268+
269+
func (ts *typeSet) Clone() *typeSet {
270+
var out typeSet
271+
ts.Iterate(out.Put)
272+
return &out
273+
}
274+
275+
// Use this as a types.Qualifier to print the name of an entity with
276+
// types.TypeString or similar without including their full package path.
277+
func emptyQualifier(*types.Package) string {
278+
return ""
279+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) 2021 Uber Technologies, Inc.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
package allfxevents
22+
23+
import (
24+
"testing"
25+
26+
"golang.org/x/tools/go/analysis/analysistest"
27+
)
28+
29+
func TestAnalyzer(t *testing.T) {
30+
analysistest.Run(t, analysistest.TestData(), Analyzer, "./...")
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package a
2+
3+
import (
4+
"fmt"
5+
6+
"go.uber.org/fx/fxevent"
7+
)
8+
9+
type fullLogger struct{}
10+
11+
func (*fullLogger) LogEvent(ev fxevent.Event) {
12+
switch ev.(type) {
13+
case *fxevent.Foo, *fxevent.Bar, *fxevent.Baz:
14+
fmt.Println(ev)
15+
case *fxevent.Qux:
16+
fmt.Println(ev)
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package a
2+
3+
import "go.uber.org/fx/fxevent"
4+
5+
type nopLogger struct{}
6+
7+
func (nopLogger) LogEvent(fxevent.Event) {
8+
// Don't do anything with the event. Should not cause a
9+
// diagnostic.
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package a
2+
3+
import (
4+
"fmt"
5+
6+
"go.uber.org/fx/fxevent"
7+
)
8+
9+
type notALogger struct{}
10+
11+
// Doesn't implement fxevent.Logger because it returns an error. This shouldn't
12+
// cause a diagnostic.
13+
14+
func (*notALogger) LogEvent(ev fxevent.Event) error {
15+
_, ok := ev.(*fxevent.Foo)
16+
fmt.Println(ok)
17+
return nil
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package a
2+
3+
import (
4+
"fmt"
5+
6+
"go.uber.org/fx/fxevent"
7+
)
8+
9+
// This logger intentionally doesn't handle everything. We don't expect any
10+
// diagnostics reported for it because it's in a test file.
11+
type partialLogger struct{}
12+
13+
func (partialLogger) LogEvent(ev fxevent.Event) {
14+
_, ok := ev.(*fxevent.Foo)
15+
fmt.Println(ok)
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package a
2+
3+
import (
4+
"log"
5+
6+
"go.uber.org/fx/fxevent"
7+
)
8+
9+
type ptrLogger struct{}
10+
11+
func (*ptrLogger) LogEvent(ev fxevent.Event) { // want `\*ptrLogger doesn't handle \[\*Bar \*Foo\]`
12+
if e, ok := ev.(*fxevent.Baz); ok {
13+
log.Print(e)
14+
}
15+
16+
if e, ok := ev.(*fxevent.Qux); ok {
17+
log.Print(e)
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package a
2+
3+
import (
4+
"fmt"
5+
"io"
6+
7+
"go.uber.org/fx/fxevent"
8+
)
9+
10+
type valueLogger struct {
11+
W io.Writer
12+
}
13+
14+
func (l valueLogger) LogEvent(ev fxevent.Event) { // want `valueLogger doesn't handle \[\*Baz \*Qux\]`
15+
switch ev.(type) {
16+
case *fxevent.Foo:
17+
fmt.Fprintln(l.W, ev)
18+
case *fxevent.Bar:
19+
fmt.Fprintln(l.W, ev)
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package fxevent
2+
3+
type (
4+
Event struct{}
5+
Logger interface{ LogEvent(Event) }
6+
)
7+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package b
2+
3+
import (
4+
"b/fxevent"
5+
"fmt"
6+
)
7+
8+
type Logger struct{}
9+
10+
var _ fxevent.Logger = Logger{}
11+
12+
func (Logger) LogEvent(ev fxevent.Event) {
13+
fmt.Println(ev)
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package fxevent
2+
3+
// This is a partial fxevent package inspired by the real fxevent package,
4+
// but with a fixed list of events we can test against.
5+
6+
type (
7+
Logger interface{ LogEvent(Event) }
8+
Event interface{ event() }
9+
Foo struct{}
10+
Bar struct{}
11+
Baz struct{}
12+
Qux struct{}
13+
)
14+
15+
func (*Foo) event() {}
16+
func (*Bar) event() {}
17+
func (*Baz) event() {}
18+
func (*Qux) event() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package fxevent
2+
3+
// Partial logger implementation in the same package as fxevent.Logger.
4+
type partialLogger struct{}
5+
6+
func (partialLogger) LogEvent(ev Event) { // want `partialLogger doesn't handle \[\*Qux\]`
7+
switch ev.(type) {
8+
case *Foo:
9+
case *Bar:
10+
case *Baz:
11+
}
12+
}

‎tools/cmd/fxlint/main.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) 2021 Uber Technologies, Inc.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
package main
22+
23+
import (
24+
"go.uber.org/fx/tools/analysis/passes/allfxevents"
25+
"golang.org/x/tools/go/analysis/multichecker"
26+
)
27+
28+
func main() {
29+
multichecker.Main(
30+
allfxevents.Analyzer,
31+
)
32+
}

‎tools/go.mod

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ module go.uber.org/fx/tools
33
go 1.16
44

55
require (
6-
golang.org/x/lint v0.0.0-20190930215403-16217165b5de
6+
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616
7+
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
8+
golang.org/x/tools v0.1.4
79
honnef.co/go/tools v0.2.0
810
)

‎tools/go.sum

+17-7
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,41 @@
11
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
22
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
33
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
4+
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
45
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
56
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
67
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
7-
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
8-
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
9-
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
8+
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
9+
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
10+
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
1011
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
11-
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
12+
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
13+
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
1214
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
1315
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
1416
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
17+
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
1518
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1619
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
20+
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1721
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1822
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1923
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
20-
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
24+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
2125
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
26+
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
27+
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
28+
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
29+
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
30+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
2231
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
2332
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
2433
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
25-
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
2634
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
27-
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
35+
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
2836
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
37+
golang.org/x/tools v0.1.4 h1:cVngSRcfgyZCzys3KYOpCFa+4dqX/Oub9tAq00ttGVs=
38+
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
2939
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
3040
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
3141
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=

0 commit comments

Comments
 (0)
Please sign in to comment.