Skip to content

Commit 93832e1

Browse files
committed
添加禁用函数检查的 lint (golangci#1)
1 parent 2dede6a commit 93832e1

File tree

3 files changed

+334
-0
lines changed

3 files changed

+334
-0
lines changed

pkg/golinters/bannedfunc.go

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package golinters
2+
3+
import (
4+
"go/ast"
5+
"io/ioutil"
6+
"os"
7+
"strings"
8+
9+
"golang.org/x/tools/go/analysis"
10+
"golang.org/x/tools/go/analysis/passes/inspect"
11+
"gopkg.in/yaml.v2"
12+
13+
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
14+
"github.com/golangci/golangci-lint/pkg/lint/linter"
15+
)
16+
17+
// Analyzer lint 插件的结构体
18+
var Analyzer = &analysis.Analyzer{
19+
Name: "bandfunc",
20+
Doc: "Checks that cannot use func",
21+
Requires: []*analysis.Analyzer{inspect.Analyzer},
22+
}
23+
24+
type configSetting struct {
25+
LinterSettings BandFunc `yaml:"linters-settings"`
26+
}
27+
28+
// BandFunc 读取配置的结构体
29+
type BandFunc struct {
30+
Funcs map[string]string `yaml:"bannedfunc,flow"`
31+
}
32+
33+
// NewCheckBannedFunc 返回检查函数
34+
func NewCheckBannedFunc() *goanalysis.Linter {
35+
return goanalysis.NewLinter(
36+
"bannedfunc",
37+
"Checks that cannot use func",
38+
[]*analysis.Analyzer{Analyzer},
39+
nil,
40+
).WithContextSetter(linterCtx).WithLoadMode(goanalysis.LoadModeSyntax)
41+
}
42+
43+
func linterCtx(lintCtx *linter.Context) {
44+
// 读取配置文件
45+
config := loadConfigFile()
46+
47+
configMap := configToConfigMap(config)
48+
49+
Analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
50+
useMap := getUsedMap(pass, configMap)
51+
for _, f := range pass.Files {
52+
ast.Inspect(f, astFunc(pass, useMap))
53+
}
54+
return nil, nil
55+
}
56+
}
57+
58+
func astFunc(pass *analysis.Pass, usedMap map[string]map[string]string) func(node ast.Node) bool {
59+
return func(node ast.Node) bool {
60+
selector, ok := node.(*ast.SelectorExpr)
61+
if !ok {
62+
return true
63+
}
64+
65+
ident, ok := selector.X.(*ast.Ident)
66+
if !ok {
67+
return true
68+
}
69+
70+
m := usedMap[ident.Name]
71+
if m == nil {
72+
return true
73+
}
74+
75+
sel := selector.Sel
76+
value, ok := m[sel.Name]
77+
if !ok {
78+
return true
79+
}
80+
pass.Reportf(node.Pos(), value)
81+
return true
82+
}
83+
}
84+
85+
// configToConfigMap 将配置文件转成 map
86+
// map[包名]map[函数名]错误提示
87+
// example:
88+
// {
89+
// time: {
90+
// Now: 不能使用 time.Now() 请使用 MiaoSiLa/missevan-go/util 下 TimeNow()
91+
// Date: xxxx
92+
// },
93+
// github.com/MiaoSiLa/missevan-go/util/time: {
94+
// TimeNow: xxxxxx
95+
// SetTimeNow: xxxxx
96+
// }
97+
// }
98+
func configToConfigMap(config configSetting) map[string]map[string]string {
99+
configMap := make(map[string]map[string]string)
100+
for k, v := range config.LinterSettings.Funcs {
101+
strs := strings.Split(k, ").")
102+
if len(strs) != 2 {
103+
continue
104+
}
105+
if len(strs[0]) <= 1 || strs[0][0] != '(' {
106+
continue
107+
}
108+
pkg, name := strs[0][1:], strs[1]
109+
if name == "" {
110+
continue
111+
}
112+
m := configMap[pkg]
113+
if m == nil {
114+
m = make(map[string]string)
115+
configMap[pkg] = m
116+
}
117+
m[name] = v
118+
}
119+
return configMap
120+
}
121+
122+
func loadConfigFile() configSetting {
123+
wd, _ := os.Getwd()
124+
f, err := ioutil.ReadFile(wd + "/.golangci.yml")
125+
if err != nil {
126+
panic(err)
127+
}
128+
return decodeFile(f)
129+
}
130+
131+
func decodeFile(b []byte) configSetting {
132+
var config configSetting
133+
err := yaml.Unmarshal(b, &config)
134+
if err != nil {
135+
panic(err)
136+
}
137+
return config
138+
}
139+
140+
// getUsedMap 将配置文件的 map 转成文件下实际变量名的 map
141+
// map[包的别名]map[函数名]错误提示
142+
// example:
143+
// {
144+
// time: {
145+
// Now: 不能使用 time.Now() 请使用 MiaoSiLa/missevan-go/util 下 TimeNow()
146+
// Date: xxxx
147+
// },
148+
// util: {
149+
// TimeNow: xxxxxx
150+
// SetTimeNow: xxxxx
151+
// }
152+
// }
153+
func getUsedMap(pass *analysis.Pass, configMap map[string]map[string]string) map[string]map[string]string {
154+
useMap := make(map[string]map[string]string)
155+
for _, item := range pass.Pkg.Imports() {
156+
if m, ok := configMap[item.Path()]; ok {
157+
useMap[item.Name()] = m
158+
}
159+
}
160+
return useMap
161+
}

pkg/golinters/bannedfunc_test.go

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package golinters
2+
3+
import (
4+
"fmt"
5+
"go/ast"
6+
"go/types"
7+
"strings"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
"golang.org/x/tools/go/analysis"
13+
"golang.org/x/tools/go/analysis/analysistest"
14+
)
15+
16+
func TestDecodeFile(t *testing.T) {
17+
assert := assert.New(t)
18+
require := require.New(t)
19+
20+
b := strings.TrimSpace(`
21+
linters-settings:
22+
bannedfunc:
23+
(time).Now: "不能使用 time.Now() 请使用 MiaoSiLa/missevan-go/util 下 TimeNow()"
24+
(github.com/MiaoSiLa/missevan-go/util).TimeNow: "aaa"
25+
`)
26+
var setting configSetting
27+
require.NotPanics(func() { setting = decodeFile([]byte(b)) })
28+
require.NotNil(setting.LinterSettings)
29+
val := setting.LinterSettings.Funcs["(time).Now"]
30+
assert.NotEmpty(val)
31+
val = setting.LinterSettings.Funcs["(github.com/MiaoSiLa/missevan-go/util).TimeNow"]
32+
assert.NotEmpty(val)
33+
}
34+
35+
func TestConfigToConfigMap(t *testing.T) {
36+
assert := assert.New(t)
37+
require := require.New(t)
38+
39+
m := map[string]string{
40+
"(time).Now": "不能使用 time.Now() 请使用 MiaoSiLa/missevan-go/util 下 TimeNow()",
41+
"(github.com/MiaoSiLa/missevan-go/util).TimeNow": "xxxx",
42+
"().": "(). 情况",
43+
").": "). 情况",
44+
}
45+
s := configSetting{LinterSettings: BandFunc{Funcs: m}}
46+
setting := configToConfigMap(s)
47+
require.Len(setting, 2)
48+
require.NotNil(setting["time"])
49+
require.NotNil(setting["time"]["Now"])
50+
assert.Equal("不能使用 time.Now() 请使用 MiaoSiLa/missevan-go/util 下 TimeNow()", setting["time"]["Now"])
51+
require.NotNil(setting["github.com/MiaoSiLa/missevan-go/util"])
52+
require.NotNil(setting["github.com/MiaoSiLa/missevan-go/util"]["TimeNow"])
53+
assert.Equal("xxxx", setting["github.com/MiaoSiLa/missevan-go/util"]["TimeNow"])
54+
assert.Nil(setting["()."])
55+
assert.Nil(setting[")."])
56+
}
57+
58+
func TestGetUsedMap(t *testing.T) {
59+
assert := assert.New(t)
60+
require := require.New(t)
61+
62+
pkg := types.NewPackage("test", "test")
63+
importPkg := []*types.Package{types.NewPackage("time", "time"),
64+
types.NewPackage("github.com/MiaoSiLa/missevan-go/util", "util")}
65+
pkg.SetImports(importPkg)
66+
pass := analysis.Pass{Pkg: pkg}
67+
m := map[string]map[string]string{
68+
"time": {"Now": "xxxx", "Date": "xxxx"},
69+
"assert": {"New": "xxxx"},
70+
"github.com/MiaoSiLa/missevan-go/util": {"TimeNow": "xxxx"},
71+
}
72+
usedMap := getUsedMap(&pass, m)
73+
require.Len(usedMap, 2)
74+
require.Len(usedMap["time"], 2)
75+
assert.NotEmpty(usedMap["time"]["Now"])
76+
assert.NotEmpty(usedMap["time"]["Date"])
77+
require.Len(usedMap["util"], 1)
78+
assert.NotEmpty(usedMap["util"]["TimeNow"])
79+
}
80+
81+
func TestAstFunc(t *testing.T) {
82+
assert := assert.New(t)
83+
84+
// 初始化测试用参数
85+
var testStr string
86+
pass := analysis.Pass{Report: func(diagnostic analysis.Diagnostic) {
87+
testStr = diagnostic.Message
88+
}}
89+
m := map[string]map[string]string{
90+
"time": {"Now": "time.Now"},
91+
"util": {"TimeNow": "util.TimeNow"},
92+
}
93+
f := astFunc(&pass, m)
94+
95+
// 测试不符合情况
96+
node := ast.SelectorExpr{X: &ast.Ident{Name: "time"}, Sel: &ast.Ident{Name: "Date"}}
97+
f(&node)
98+
assert.Empty(testStr)
99+
node = ast.SelectorExpr{X: &ast.Ident{Name: "assert"}, Sel: &ast.Ident{Name: "New"}}
100+
f(&node)
101+
assert.Empty(testStr)
102+
103+
// 测试符合情况
104+
node = ast.SelectorExpr{X: &ast.Ident{Name: "time"}, Sel: &ast.Ident{Name: "Now"}}
105+
f(&node)
106+
assert.Equal("time.Now", testStr)
107+
node = ast.SelectorExpr{X: &ast.Ident{Name: "util"}, Sel: &ast.Ident{Name: "TimeNow"}}
108+
f(&node)
109+
assert.Equal("util.TimeNow", testStr)
110+
}
111+
112+
func TestFile(t *testing.T) {
113+
assert := assert.New(t)
114+
require := require.New(t)
115+
116+
b := strings.TrimSpace(`
117+
linters-settings:
118+
bannedfunc:
119+
(time).Now: "禁止使用 time.Now"
120+
(time).Unix: "禁止使用 time.Unix"
121+
`)
122+
var setting configSetting
123+
require.NotPanics(func() { setting = decodeFile([]byte(b)) })
124+
var m map[string]map[string]string
125+
m = configToConfigMap(setting)
126+
Analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
127+
useMap := getUsedMap(pass, m)
128+
for _, f := range pass.Files {
129+
ast.Inspect(f, astFunc(pass, useMap))
130+
}
131+
return nil, nil
132+
}
133+
// NOTICE: 因为配置的初始化函数将 GOPATH 设置为当前目录
134+
// 所以只能 import 内置包和当前目录下的包
135+
files := map[string]string{
136+
"a/b.go": `package main
137+
138+
import (
139+
"fmt"
140+
"time"
141+
)
142+
143+
func main() {
144+
fmt.Println(time.Now())
145+
_ = time.Now()
146+
_ = time.Now().Unix()
147+
_ = time.Unix(10000,0)
148+
}
149+
150+
`}
151+
dir, cleanup, err := analysistest.WriteFiles(files)
152+
require.NoError(err)
153+
defer cleanup()
154+
var got []string
155+
t2 := errorfunc(func(s string) { got = append(got, s) }) // a fake *testing.T
156+
analysistest.Run(t2, dir, Analyzer, "a")
157+
want := []string{
158+
`a/b.go:9:14: unexpected diagnostic: 禁止使用 time.Now`,
159+
`a/b.go:10:6: unexpected diagnostic: 禁止使用 time.Now`,
160+
`a/b.go:11:6: unexpected diagnostic: 禁止使用 time.Now`,
161+
`a/b.go:12:6: unexpected diagnostic: 禁止使用 time.Unix`,
162+
}
163+
assert.Equal(want, got)
164+
}
165+
166+
type errorfunc func(string)
167+
168+
func (f errorfunc) Errorf(format string, args ...interface{}) {
169+
f(fmt.Sprintf(format, args...))
170+
}

pkg/lint/lintersdb/manager.go

+3
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,9 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
885885
WithPresets(linter.PresetStyle).
886886
WithURL("https://github.com/bombsimon/wsl"),
887887

888+
linter.NewConfig(golinters.NewCheckBannedFunc()).
889+
WithPresets(linter.PresetStyle),
890+
888891
// nolintlint must be last because it looks at the results of all the previous linters for unused nolint directives
889892
linter.NewConfig(golinters.NewNoLintLint(noLintLintCfg)).
890893
WithSince("v1.26.0").

0 commit comments

Comments
 (0)