9
9
"go/token"
10
10
"go/types"
11
11
"strconv"
12
+ "strings"
12
13
13
14
"github.com/ettle/strcase"
14
15
"golang.org/x/tools/go/analysis"
@@ -22,6 +23,7 @@ type Options struct {
22
23
NoMixedArgs bool // Enforce not mixing key-value pairs and attributes (default true).
23
24
KVOnly bool // Enforce using key-value pairs only (overrides NoMixedArgs, incompatible with AttrOnly).
24
25
AttrOnly bool // Enforce using attributes only (overrides NoMixedArgs, incompatible with KVOnly).
26
+ NoGlobal string // Enforce not using global loggers ("all" or "default").
25
27
ContextOnly bool // Enforce using methods that accept a context.
26
28
StaticMsg bool // Enforce using static log messages.
27
29
NoRawKeys bool // Enforce using constants instead of raw keys.
@@ -43,11 +45,19 @@ func New(opts *Options) *analysis.Analyzer {
43
45
if opts .KVOnly && opts .AttrOnly {
44
46
return nil , fmt .Errorf ("sloglint: Options.KVOnly and Options.AttrOnly: %w" , errIncompatible )
45
47
}
48
+
49
+ switch opts .NoGlobal {
50
+ case "" , "all" , "default" :
51
+ default :
52
+ return nil , fmt .Errorf ("sloglint: Options.NoGlobal=%s: %w" , opts .NoGlobal , errInvalidValue )
53
+ }
54
+
46
55
switch opts .KeyNamingCase {
47
56
case "" , snakeCase , kebabCase , camelCase , pascalCase :
48
57
default :
49
58
return nil , fmt .Errorf ("sloglint: Options.KeyNamingCase=%s: %w" , opts .KeyNamingCase , errInvalidValue )
50
59
}
60
+
51
61
run (pass , opts )
52
62
return nil , nil
53
63
},
@@ -60,30 +70,34 @@ var (
60
70
)
61
71
62
72
func flags (opts * Options ) flag.FlagSet {
63
- fs := flag .NewFlagSet ("sloglint" , flag .ContinueOnError )
73
+ fset := flag .NewFlagSet ("sloglint" , flag .ContinueOnError )
64
74
65
75
boolVar := func (value * bool , name , usage string ) {
66
- fs .Func (name , usage , func (s string ) error {
76
+ fset .Func (name , usage , func (s string ) error {
67
77
v , err := strconv .ParseBool (s )
68
78
* value = v
69
79
return err
70
80
})
71
81
}
72
82
83
+ strVar := func (value * string , name , usage string ) {
84
+ fset .Func (name , usage , func (s string ) error {
85
+ * value = s
86
+ return nil
87
+ })
88
+ }
89
+
73
90
boolVar (& opts .NoMixedArgs , "no-mixed-args" , "enforce not mixing key-value pairs and attributes (default true)" )
74
91
boolVar (& opts .KVOnly , "kv-only" , "enforce using key-value pairs only (overrides -no-mixed-args, incompatible with -attr-only)" )
75
92
boolVar (& opts .AttrOnly , "attr-only" , "enforce using attributes only (overrides -no-mixed-args, incompatible with -kv-only)" )
93
+ strVar (& opts .NoGlobal , "no-global" , "enforce not using global loggers (all|default)" )
76
94
boolVar (& opts .ContextOnly , "context-only" , "enforce using methods that accept a context" )
77
95
boolVar (& opts .StaticMsg , "static-msg" , "enforce using static log messages" )
78
96
boolVar (& opts .NoRawKeys , "no-raw-keys" , "enforce using constants instead of raw keys" )
97
+ strVar (& opts .KeyNamingCase , "key-naming-case" , "enforce a single key naming convention (snake|kebab|camel|pascal)" )
79
98
boolVar (& opts .ArgsOnSepLines , "args-on-sep-lines" , "enforce putting arguments on separate lines" )
80
99
81
- fs .Func ("key-naming-case" , "enforce a single key naming convention (snake|kebab|camel|pascal)" , func (s string ) error {
82
- opts .KeyNamingCase = s
83
- return nil
84
- })
85
-
86
- return * fs
100
+ return * fset
87
101
}
88
102
89
103
var slogFuncs = map [string ]int { // funcName:argsPos
@@ -139,17 +153,30 @@ func run(pass *analysis.Pass, opts *Options) {
139
153
return
140
154
}
141
155
142
- argsPos , ok := slogFuncs [fn .FullName ()]
156
+ name := fn .FullName ()
157
+ argsPos , ok := slogFuncs [name ]
143
158
if ! ok {
144
159
return
145
160
}
146
161
162
+ switch opts .NoGlobal {
163
+ case "all" :
164
+ if strings .HasPrefix (name , "log/slog." ) || globalLoggerUsed (pass .TypesInfo , call .Fun ) {
165
+ pass .Reportf (call .Pos (), "global logger should not be used" )
166
+ }
167
+ case "default" :
168
+ if strings .HasPrefix (name , "log/slog." ) {
169
+ pass .Reportf (call .Pos (), "default logger should not be used" )
170
+ }
171
+ }
172
+
147
173
if opts .ContextOnly {
148
174
typ := pass .TypesInfo .TypeOf (call .Args [0 ])
149
175
if typ != nil && typ .String () != "context.Context" {
150
176
pass .Reportf (call .Pos (), "methods without a context should not be used" )
151
177
}
152
178
}
179
+
153
180
if opts .StaticMsg && ! staticMsg (call .Args [argsPos - 1 ]) {
154
181
pass .Reportf (call .Pos (), "message should be a string literal or a constant" )
155
182
}
@@ -189,6 +216,7 @@ func run(pass *analysis.Pass, opts *Options) {
189
216
if opts .NoRawKeys && rawKeysUsed (pass .TypesInfo , keys , attrs ) {
190
217
pass .Reportf (call .Pos (), "raw keys should not be used" )
191
218
}
219
+
192
220
if opts .ArgsOnSepLines && argsOnSameLine (pass .Fset , call , keys , attrs ) {
193
221
pass .Reportf (call .Pos (), "arguments should be put on separate lines" )
194
222
}
@@ -206,6 +234,19 @@ func run(pass *analysis.Pass, opts *Options) {
206
234
})
207
235
}
208
236
237
+ func globalLoggerUsed (info * types.Info , expr ast.Expr ) bool {
238
+ selector , ok := expr .(* ast.SelectorExpr )
239
+ if ! ok {
240
+ return false
241
+ }
242
+ ident , ok := selector .X .(* ast.Ident )
243
+ if ! ok {
244
+ return false
245
+ }
246
+ obj := info .ObjectOf (ident )
247
+ return obj .Parent () == obj .Pkg ().Scope ()
248
+ }
249
+
209
250
func staticMsg (expr ast.Expr ) bool {
210
251
switch msg := expr .(type ) {
211
252
case * ast.BasicLit : // e.g. slog.Info("msg")
0 commit comments